/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Initial developer(s): Robert Hodges * Contributor(s): */ package com.continuent.tungsten.common.sockets; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import javax.net.ssl.SSLHandshakeException; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.continuent.tungsten.common.config.cluster.ConfigurationException; import com.continuent.tungsten.common.security.AuthenticationInfo; import com.continuent.tungsten.common.security.SecurityConf; import com.continuent.tungsten.common.security.SecurityHelper; import org.junit.Assert; /** * Implements a test of client and server socket wrappers using SSL and non-SSL * connections. * <p/> * IMPORTANT NOTE! To run this test in Eclipse set the working directory to * commons/build/work. Otherwise you won't be able to find the * sample.security.properties file. * * @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a> * @version 1.0 */ public class SocketWrapperTest { private static Logger logger = Logger.getLogger(SocketWrapperTest.class); private EchoServer server; private SocketHelper helper = new SocketHelper(); Level debugLevel_socket = null; /** * Make sure that tests are terminated if prerequirements are not met. * * @throws ConfigurationException */ @Before public void checkConfiguration() throws ConfigurationException { if (!System.getProperty("user.dir").endsWith("commons/build/work")) { throw new ConfigurationException("\n\tInvalid working directory : " + System.getProperty("user.dir") + ".\n" + "\tWorking directory must be " + "../commons/build/work in order to test work.\n" + "\tHINT: in Eclipse, set working directory to " + "${workspace_loc:commons/build/work} in arguments."); } } /** * Terminate echo server if still running. * * @throws ClassNotFoundException */ @After public void teardown() { if (server != null) { logger.info("Shutting down echo server..."); server.shutdown(); } } /** * Verify that we can connect using a non-SSL socket and get a value back * from a server. */ @Test public void testNonSSLConnection() throws Exception { logger.info("### testNonSSLConnection"); verifyConnection(2113, false, null, null, null, false); } /** * Verify that we can connect using an SSL socket and get a value back from * a server that also speaks SSL. */ @Test public void testSSLConnection() throws Exception { logger.info("### testSSLConnection"); AuthenticationInfo securityInfo = helper.loadSecurityProperties(); verifyConnection(2114, true, null, null, securityInfo, null, false); } /** * Verify that we can connect using an SSL socket and a predefined alias * inside the keystore and get a value back from a server that also speaks * SSL. */ @Test public void testSSLConnection_keystoreWithAlias() throws Exception { logger.info("### testSSLConnection with alias selection"); AuthenticationInfo securityInfo = helper .loadSecurityProperties_keystoreWithAlias(); String server_masterAlias = securityInfo .getKeystoreAliasForConnectionType(SecurityConf.KEYSTORE_ALIAS_REPLICATOR_MASTER_TO_SLAVE); try { verifyConnection(2114, true, server_masterAlias, server_masterAlias, securityInfo, securityInfo, false); } catch (Exception e) { assertFalse("No exception should have been thrown", true); } } /** * Verify that when specifying a wrong server alias, the server cannot * start. This confirms that the alias selection works */ @Test public void testSSLConnection_keystoreWithAlias_wrong_server_alias() throws ConfigurationException, Exception { logger.info("### testSSLConnection with alias selection: wrong server alias"); AuthenticationInfo securityInfo = helper .loadSecurityProperties_keystoreWithAlias(); String client_slaveAlias = securityInfo .getKeystoreAliasForConnectionType(SecurityConf .KEYSTORE_ALIAS_REPLICATOR_MASTER_TO_SLAVE); try { // Change debug level so that trace messages are shown debugLevel_socket = LogManager .getLogger( Class.forName("com.continuent.tungsten.common" + ".sockets.AliasSelectorKeyManager")) .getLevel(); LogManager .getLogger( Class.forName("com.continuent.tungsten.common" + ".sockets.AliasSelectorKeyManager")) .setLevel(Level.TRACE); verifyConnection( 2114, true, "alias_that_does_not_exist_but it's ok it's the expected result", client_slaveAlias, securityInfo, securityInfo, true); assertTrue( "The server should not have started: we used a non existing " + "alias in the keystore", false); } catch (SSLHandshakeException e) { assertTrue( "This exception is expected as we're trying to use a non " + "existant server alias in the keystore", true); } finally { LogManager .getLogger( Class.forName("com.continuent.tungsten.common.sockets.AliasSelectorKeyManager")) .setLevel(debugLevel_socket); } } /** * Verify that a non-SSL connection fails to connect to an SSL server. */ @Test public void testSSLConnectionIncompatibility() throws Exception { // Start an SSL server. logger.info("### testSSLConnectionIncompatibility"); helper.loadSecurityProperties(); server = new EchoServer("127.0.0.1", 2115, true, null, null, true); server.start(); // Connect with non-SSL socket. ClientSocketWrapper clientWrapper = new ClientSocketWrapper(); clientWrapper.setAddress(new InetSocketAddress("127.0.0.1", 2115)); clientWrapper.connect(); // Write data to the server. Socket sock = clientWrapper.getSocket(); OutputStream os = sock.getOutputStream(); byte[] buf1 = "This is data".getBytes(); os.write(buf1, 0, buf1.length); // Inquire as to the echo server's health. It should not be good. Throwable serverError = null; for (int i = 0; i < 10; i++) { serverError = server.getThrowable(); if (serverError != null) break; else Thread.sleep(500); } Assert.assertNotNull("Server should have error", serverError); logger.info("Found expected server error: " + serverError.toString()); clientWrapper.close(); } /** * Verify that multiple non-SSL clients can connect to the server and that * the server can stop when the clients are idle. */ @Test public void testNonSSLClientsBasic() throws Exception { logger.info("### testNonSSLClientsBasic"); verifyClients(2116, 5, false, false); } /** * Verify that multiple SSL clients can connect to the server and that the * server can stop when the clients are idle. */ @Test public void testSSLClientsBasic() throws Exception { logger.info("### testSSLClientsBasic"); AuthenticationInfo securityInfo = helper.loadSecurityProperties(); verifyClients(2117, 5, true, securityInfo, false); } /** * Implement verification using a simple echo server. */ private void verifyConnection(int port, boolean useSSL, String serverKeystoreAlias, String clientKeystoreAlias, AuthenticationInfo securityInfo, boolean silentFail) throws Exception { verifyConnection(port, useSSL, serverKeystoreAlias, clientKeystoreAlias, securityInfo, null, silentFail); } private void verifyConnection(int port, boolean useSSL, String serverKeystoreAlias, String clientKeystoreAlias, AuthenticationInfo securityInfo, AuthenticationInfo serverSecurityInfo, boolean silentFail) throws Exception, ConfigurationException { server = new EchoServer("127.0.0.1", port, useSSL, serverKeystoreAlias, serverSecurityInfo, silentFail); server.start(); ClientSocketWrapper clientWrapper = new ClientSocketWrapper(); clientWrapper.setUseSSL(useSSL); if (useSSL) { Assert.assertTrue(securityInfo != null); } clientWrapper.setAddress(new InetSocketAddress("127.0.0.1", port)); clientWrapper.connect(); Socket sock = clientWrapper.getSocket(); Assert.assertNotNull("Expected to get a client socket", sock); String echoValue = helper.echo(sock, "hello"); logger.info("Echoed value: " + echoValue); Assert.assertEquals("Expect echo to match", "hello", echoValue); clientWrapper.close(); } public void verifyClients(int port, int numberOfClients, boolean useSSL, boolean silentFail) throws Exception { verifyClients(port, numberOfClients, useSSL, null, silentFail); } /** * Verify that multiple clients can connect to the server and that the * server can stop when the clients are idle. */ public void verifyClients(int port, int numberOfClients, boolean useSSL, AuthenticationInfo securityInfo, boolean silentFail) throws Exception { // Start a server. server = new EchoServer("127.0.0.1", port, useSSL, null, null, silentFail); server.start(); // Launch echo clients with 100ms think time between // requests. EchoClient[] clients = new EchoClient[numberOfClients]; if (useSSL) { Assert.assertTrue(securityInfo != null); } for (int i = 0; i < clients.length; i++) { EchoClient client = new EchoClient("127.0.0.1", port, useSSL, 100); client.setEnabledCiphers(SecurityHelper.getCiphers()); client.setEnabledProtocols(SecurityHelper.getProtocols()); client.start(); clients[i] = client; } // Bide a wee. Thread.sleep(5000); // Stop the threads and confirm that each has processed at least 10 echo // requests. try { for (EchoClient client : clients) { // Ensure we can shut down the client. Assert.assertTrue("Shut down client: " + client.getName(), client.shutdown()); Assert.assertTrue("Expect least 10 operations per client: " + client.getName(), client.getEchoCount() >= 10); Assert.assertNull( "Do not expect errors for client: " + client.getName(), client.getThrowable()); } } finally { // Shut down all clients. for (EchoClient client : clients) client.shutdown(); } // Ensure the echo server is OK. Assert.assertNull("Echo server does not have any errors", server.getThrowable()); } }