/******************************************************************************* * Copyright 2013-2016 alladin-IT GmbH * * 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. ******************************************************************************/ package at.alladin.rmbt.qos.testserver; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.nio.channels.DatagramChannel; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import at.alladin.rmbt.qos.testserver.ServerPreferences.ServiceSetting; import at.alladin.rmbt.qos.testserver.ServerPreferences.TestServerServiceEnum; import at.alladin.rmbt.qos.testserver.ServerPreferences.UdpPort; import at.alladin.rmbt.qos.testserver.entity.TestCandidate; import at.alladin.rmbt.qos.testserver.servers.AbstractUdpServer; import at.alladin.rmbt.qos.testserver.service.EventJob.EventType; import at.alladin.rmbt.qos.testserver.service.ServiceManager; import at.alladin.rmbt.qos.testserver.tcp.TcpMultiClientServer; import at.alladin.rmbt.qos.testserver.tcp.TcpWatcherRunnable; import at.alladin.rmbt.qos.testserver.udp.NioUdpMultiClientServer; import at.alladin.rmbt.qos.testserver.udp.UdpMultiClientServer; import at.alladin.rmbt.qos.testserver.udp.UdpTestCandidate; import at.alladin.rmbt.qos.testserver.udp.UdpWatcherRunnable; import at.alladin.rmbt.qos.testserver.util.LoggingService; import at.alladin.rmbt.qos.testserver.util.RuntimeGuardService; import at.alladin.rmbt.qos.testserver.util.TestServerConsole; import at.alladin.rmbt.util.Randomizer; /** * * @author lb * */ public class TestServer { /** * major version number */ public final static String TEST_SERVER_VERSION_MAJOR = "3"; /** * minor version number */ public final static String TEST_SERVER_VERSION_MINOR = "1"; /** * patch version number */ public final static String TEST_SERVER_VERSION_PATCH = "1"; /** * server code name * <ul> * <li><b>0.01 - 0.61</b> - ANONYMOUS PROXY</li> * <li><b>0.61 - 0.91</b> - BANDWIDTH THROTTLING * <p>major changes: support for multiple tests with single connection (client and server side)</p> * </li> * <li><b>1.00 - 1.04</b> - CENSORSHIP'N CONTROL * <p>major changes: implement rest services</p> * </li> * <li><b>2.00 - 2.19</b> - DROPPED PACKET * <p>major changes: voip test, rmbtutil</p></li> * <li><b>2.20 - 2.30</b> - DROPPED PACKET * <p>major changes: non blocking udp server for udp and voip tests</p> * </li> * <li><b>3.0.0 - x.x.x</b> - ??? * <p>major changes: syslog support, logging framework: log4j</p> * <p>major fixes: udp nio empty array fix</p> * </li> * </ul> */ public final static String TEST_SERVER_CODENAME = "DROPPED PACKET"; /** * full version string */ public final static String TEST_SERVER_VERSION_NAME = "QoSTS \"" + TEST_SERVER_CODENAME + "\" " + TEST_SERVER_VERSION_MAJOR + "." + TEST_SERVER_VERSION_MINOR; /** * key path */ public final static String QOS_KEY_FILE = "crt/qosserver.jks"; /** * key type */ public final static String QOS_KEY_TYPE = "JKS"; /** * key password */ public final static String QOS_KEY_PASSWORD = "123456qwertz"; public final static String RMBT_ENCRYPTION_STRING = "TLS"; public final static boolean USE_FIXED_THREAD_POOL = true; public final static int MAX_THREADS = 200; public final static ConcurrentHashMap<Integer, List<AbstractUdpServer<?>>> udpServerMap = new ConcurrentHashMap<Integer, List<AbstractUdpServer<?>>>(); public final static ConcurrentHashMap<Integer, List<TcpMultiClientServer>> tcpServerMap = new ConcurrentHashMap<Integer, List<TcpMultiClientServer>>(); /** * server socket list (=awaiting client test requests) */ public volatile static List<ServerSocket> serverSocketList = new ArrayList<ServerSocket>(); public static SSLServerSocketFactory sslServerSocketFactory; public static ServerPreferences serverPreferences; public final static ServiceManager serviceManager = new ServiceManager(); public final static Set<ClientHandler> clientHandlerSet = new HashSet<>(); /** * */ public final static Randomizer randomizer = new Randomizer(8000, 12000, 3); /** * is used for all control connection threads */ private static ExecutorService mainServerPool; /** * is used for all other threads */ private static final ExecutorService COMMON_THREAD_POOL = Executors.newCachedThreadPool(); /** * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { TestServerConsole console = new TestServerConsole(); System.setErr(console); System.setOut(console); try { if (args.length>0) { serverPreferences = new ServerPreferences(args); } else { serverPreferences = new ServerPreferences(); } } catch (TestServerException e) { ServerPreferences.writeErrorString(); e.printStackTrace(); System.exit(0); } if (!LoggingService.IS_LOGGING_AVAILABLE) { throw new TestServerException("ERROR: All logging disabled! Cannot start server. Please enable at least one logging target [syslog, console, file]", null); } TestServerConsole.log("\nStarting QoSTestServer (" + TEST_SERVER_VERSION_NAME + ") with settings: \n" + serverPreferences.toString() + "\n\n", -1, TestServerServiceEnum.TEST_SERVER); console.start(); if (!USE_FIXED_THREAD_POOL) { mainServerPool = Executors.newCachedThreadPool(); } else { mainServerPool = Executors.newFixedThreadPool(serverPreferences.getMaxThreads()); } try { if (serverPreferences.useSsl()) { /******************************* * initialize SSLContext and SSLServerSocketFactory: */ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); // Load the JKS key chain KeyStore ks = KeyStore.getInstance(QOS_KEY_TYPE); InputStream fis = TestServer.class.getResourceAsStream(QOS_KEY_FILE);//new FileInputStream(serverKey); ks.load(fis, QOS_KEY_PASSWORD.toCharArray()); fis.close(); kmf.init(ks, QOS_KEY_PASSWORD.toCharArray()); final SSLContext sslContext = SSLContext.getInstance("TLS"); // Initialize the SSL context sslContext.init(kmf.getKeyManagers(), new TrustManager[] {getTrustingManager()}, new SecureRandom()); sslServerSocketFactory = (SSLServerSocketFactory) sslContext.getServerSocketFactory(); } for (InetAddress addr : serverPreferences.getInetAddrBindToSet()) { ServerSocket serverSocket; if (serverPreferences.useSsl()) { serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(); } else { serverSocket = new ServerSocket(); } serverSocket.setReuseAddress(true); serverSocket.bind(new InetSocketAddress(addr, serverPreferences.getServerPort())); serverSocketList.add(serverSocket); Thread mainThread = new Thread(new QoSService(mainServerPool, serverSocket, null)); mainThread.start(); } Iterator<UdpPort> portIterator = serverPreferences.getUdpPortSet().iterator(); while (portIterator.hasNext()) { final List<AbstractUdpServer<?>> udpServerList = new ArrayList<AbstractUdpServer<?>>(); final UdpPort udpPort = portIterator.next(); for (InetAddress addr : serverPreferences.getInetAddrBindToSet()) { AbstractUdpServer<?> udpServer = null; try { //udpServer = new UdpMultiClientServer(port, addr); udpServer = udpPort.isNio ? new NioUdpMultiClientServer(udpPort.port, addr) : new UdpMultiClientServer(udpPort.port, addr); } catch (Exception e) { TestServerConsole.error("TestServer INIT; Opening UDP Server on port: " + udpPort, e, 0, TestServerServiceEnum.TEST_SERVER); } if (udpServer != null) { udpServerList.add(udpServer); getCommonThreadPool().execute(udpServer); } } if (udpServerList.size() > 0) { udpServerMap.put(udpPort.port, udpServerList); } } //start UDP watcher service: final UdpWatcherRunnable udpWatcherRunnable = new UdpWatcherRunnable(); serviceManager.addService(udpWatcherRunnable); //start TCP watcher service: final TcpWatcherRunnable tcpWatcherRunnable = new TcpWatcherRunnable(); serviceManager.addService(tcpWatcherRunnable); //register runtime guard service: final RuntimeGuardService runtimeGuardService = new RuntimeGuardService(); serviceManager.addService(runtimeGuardService); //dispatch onStart event: serviceManager.dispatchEvent(EventType.ON_TEST_SERVER_START); //finally, start all plugins & services: for (ServiceSetting service : serverPreferences.getPluginMap().values()) { TestServerConsole.log("Starting plugin '" + service.getName() + "'...", 0, TestServerServiceEnum.TEST_SERVER); service.start(); } //add shutdown hook (ctrl+c): Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { TestServerConsole.log("CTRL-C. Close! ", -1, TestServerServiceEnum.TEST_SERVER); shutdown(); } }); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } public static void shutdown() { TestServerConsole.log("Shutting down QoS TestServer...", 0, TestServerServiceEnum.TEST_SERVER); //stop all plugins & services: for (ServiceSetting service : serverPreferences.getPluginMap().values()) { service.stop(); TestServerConsole.log("Plugin '" + service.getName() + "' stopped.", 0, TestServerServiceEnum.TEST_SERVER); } //dispatch onStop event: serviceManager.dispatchEvent(EventType.ON_TEST_SERVER_STOP); Map<String, Object> serviceResultMap = serviceManager.shutdownAll(true); if (serviceResultMap != null) { for (Entry<String, Object> entry : serviceResultMap.entrySet()) { if (entry.getValue() != null) { TestServerConsole.log("Service '" + entry.getKey() + "' result: " + entry.getValue(), 0, TestServerServiceEnum.TEST_SERVER); } } } mainServerPool.shutdownNow(); try { mainServerPool.awaitTermination(4L, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } finally { for (ServerSocket serverSocket : serverSocketList) { if (serverSocket != null && !serverSocket.isClosed()) { try { serverSocket.close(); System.out.println("\nServerSocket: " + serverSocket + " closed."); } catch (IOException e) { e.printStackTrace(); } } } } } /** * * @param port * @param isSsl * @param inetAddress * @return * @throws IOException */ public static ServerSocket createServerSocket(int port, boolean isSsl, InetAddress inetAddress) throws IOException { ServerSocket socket = null; SocketAddress sa = new InetSocketAddress(inetAddress, port); if (!isSsl || !TestServer.serverPreferences.useSsl()) { socket = new ServerSocket(); } else { socket = TestServer.sslServerSocketFactory.createServerSocket(); } try { socket.bind(sa); } catch (Exception e) { TestServerConsole.errorReport("TCP " + port, "TCP Socket on port " + port, e, 0, TestServerServiceEnum.TCP_SERVICE); throw e; } socket.setReuseAddress(true); return socket; } /** * * @param port * @return * @throws Exception */ public static DatagramSocket createDatagramSocket(int port, InetAddress addr) throws Exception { if (addr == null) { return new DatagramSocket(port); } else { return new DatagramSocket(port, addr); } } /** * * @param port * @param addr * @return * @throws Exception */ public static DatagramChannel createDatagramChannel(int port, InetAddress addr) throws Exception { final DatagramChannel channel = DatagramChannel.open(); if (addr == null) { channel.bind(new InetSocketAddress(port)); } else { channel.bind(new InetSocketAddress(addr, port)); } return channel; } public static TrustManager getTrustingManager() { return new javax.net.ssl.X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } public void checkClientTrusted(final X509Certificate[] certs, final String authType) throws CertificateException { System.out.println("[TRUSTING] checkClientTrusted: " + Arrays.toString(certs) + " - " + authType); } public void checkServerTrusted(final X509Certificate[] certs, final String authType) throws CertificateException { System.out.println("[TRUSTING] checkServerTrusted: " + Arrays.toString(certs) + " - " + authType); } }; } public static SSLContext getSSLContext(final String keyResource, final String certResource) throws NoSuchAlgorithmException, KeyManagementException { X509Certificate _cert = null; try { if (certResource != null) { final CertificateFactory cf = CertificateFactory.getInstance("X.509"); _cert = (X509Certificate) cf.generateCertificate(TestServer.class.getClassLoader().getResourceAsStream( certResource)); } } catch (final Exception e) { e.printStackTrace(); } final X509Certificate cert = _cert; TrustManagerFactory tmf = null; try { if (cert != null) { final KeyStore ks = KeyStore.getInstance(""); ks.load(TestServer.class.getClassLoader().getResourceAsStream(keyResource), "199993ec".toCharArray()); ks.setCertificateEntry("crt", cert); tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks); } } catch (Exception e) { e.printStackTrace(); } final TrustManager tm; if (cert == null) tm = getTrustingManager(); else tm = new javax.net.ssl.X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] { cert }; } public void checkClientTrusted(final X509Certificate[] certs, final String authType) throws CertificateException { System.out.println("checkClientTrusted: " + Arrays.toString(certs) + " - " + authType); } public void checkServerTrusted(final X509Certificate[] certs, final String authType) throws CertificateException { System.out.println("checkServerTrusted: " + Arrays.toString(certs) + " - " + authType); if (certs == null) throw new CertificateException(); for (final X509Certificate c : certs) if (cert.equals(c)) return; throw new CertificateException(); } }; final TrustManager[] trustManagers = new TrustManager[] { tm }; javax.net.ssl.SSLContext sc; sc = javax.net.ssl.SSLContext.getInstance(RMBT_ENCRYPTION_STRING); sc.init(null, trustManagers, new java.security.SecureRandom()); return sc; } /** * * @return */ public static ConcurrentHashMap<Integer, List<TcpMultiClientServer>> getTcpServerMap() { return tcpServerMap; } /** * * @param port */ public static synchronized void unregisterTcpServer(Integer port) { if (TestServer.tcpServerMap.remove(port) != null) { TestServerConsole.log("Removed TCP server object on port: " + port, 1, TestServerServiceEnum.TCP_SERVICE); } } /** * * @param port * @param server * @throws Exception */ public static List<TcpMultiClientServer> registerTcpCandidate(Integer port, Socket socket) throws Exception { List<TcpMultiClientServer> tcpServerList; synchronized(TestServer.tcpServerMap) { //if port not mapped create complete tcp server list if ((tcpServerList = TestServer.tcpServerMap.get(port)) == null) { tcpServerList = new ArrayList<>(); for (InetAddress addr : TestServer.serverPreferences.getInetAddrBindToSet()) { tcpServerList.add(new TcpMultiClientServer(port, addr)); } TestServer.tcpServerMap.put(port, tcpServerList); } else { //there are some tcp servers active on this port, compare ip list with tcp server list and create missing servers tcpServerList = TestServer.tcpServerMap.get(port); Set<InetAddress> inetAddrSet = new HashSet<>(); for (TcpMultiClientServer tcpServer : tcpServerList) { inetAddrSet.add(tcpServer.getInetAddr()); } Set<InetAddress> inetAddrBindToSet = new HashSet<>(TestServer.serverPreferences.getInetAddrBindToSet()); inetAddrBindToSet.removeAll(inetAddrSet); for (InetAddress addr : inetAddrBindToSet) { tcpServerList.add(new TcpMultiClientServer(port, addr)); } } //if ip check is enabled register the candidate's ip on the server's whitelist if (TestServer.serverPreferences.isIpCheck()) { for (TcpMultiClientServer tcpServer : tcpServerList) { tcpServer.registerCandidate(socket.getInetAddress(), TestCandidate.DEFAULT_TTL); } } //prepare all servers on this port for incoming connections (open sockets/refresh ttl) for (TcpMultiClientServer tcpServer : tcpServerList) { tcpServer.prepare(); } } return tcpServerList; } /** * * @param <T> * @param localAddr * @param port * @param uuid * @param udpData * @return */ @SuppressWarnings("unchecked") public static synchronized <T extends Closeable> AbstractUdpServer<T> registerUdpCandidate(final InetAddress localAddr, final int port, final String uuid, final UdpTestCandidate udpData) { try { TestServerConsole.log("Trying to register UDP Candidate on " + localAddr + ":" + port , 0, TestServerServiceEnum.UDP_SERVICE); for (AbstractUdpServer<?> udpServer : TestServer.udpServerMap.get(port)) { TestServerConsole.log("Comparing: " + localAddr + " <-> " + udpServer.getAddress(), 0, TestServerServiceEnum.UDP_SERVICE); if (udpServer.getAddress().equals(localAddr)) { TestServerConsole.log("Registering UDP Candidate on " + localAddr + ":" + port , 0, TestServerServiceEnum.UDP_SERVICE); TestServerConsole.log("Registering UDP Candidate for UdpServer: " + udpServer.toString(), 2, TestServerServiceEnum.UDP_SERVICE); ((AbstractUdpServer<T>) udpServer).getIncomingMap().put(uuid, udpData); return (AbstractUdpServer<T>) udpServer; } } } catch (Exception e) { TestServerConsole.error("Register UDP candidate on port: " + port, e, 1, TestServerServiceEnum.UDP_SERVICE); } return null; } /** * * @return */ public static ExecutorService getCommonThreadPool() { return COMMON_THREAD_POOL; } }