diff --git a/LaunchServer/build.gradle b/LaunchServer/build.gradle index 9d6f916b..6bfad37b 100644 --- a/LaunchServer/build.gradle +++ b/LaunchServer/build.gradle @@ -43,7 +43,7 @@ compile project(':libLauncher') // pack compileOnly 'org.spigotmc:spigot-api:1.8-R0.1-SNAPSHOT' // api compileOnly 'net.md-5:bungeecord-api:1.8-SNAPSHOT' // api - + compileOnly group: 'io.netty', name: 'netty-all', version: '4.1.28.Final' compileOnly 'org.ow2.asm:asm-debug-all:5.0.4' bundleOnly 'org.ow2.asm:asm-all:5.0.4' bundle 'org.apache.logging.log4j:log4j-core:2.9.0' diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/socket/NettyServerSocketHandler.java b/LaunchServer/src/main/java/ru/gravit/launchserver/socket/NettyServerSocketHandler.java new file mode 100644 index 00000000..3a9a394e --- /dev/null +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/socket/NettyServerSocketHandler.java @@ -0,0 +1,219 @@ +package ru.gravit.launchserver.socket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import ru.gravit.launcher.LauncherAPI; +import ru.gravit.launcher.hasher.HashedEntry; +import ru.gravit.launcher.serialize.HInput; +import ru.gravit.launcher.serialize.HOutput; +import ru.gravit.launcher.ssl.LauncherKeyStore; +import ru.gravit.launcher.ssl.LauncherTrustManager; +import ru.gravit.launchserver.LaunchServer; +import ru.gravit.launchserver.response.Response; +import ru.gravit.utils.helper.CommonHelper; +import ru.gravit.utils.helper.LogHelper; +import ru.gravit.utils.helper.VerifyHelper; + +import javax.net.ssl.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +public final class NettyServerSocketHandler implements Runnable, AutoCloseable { + private static SSLServerSocketFactory ssf; + private static final ThreadFactory THREAD_FACTORY = r -> CommonHelper.newThread("Network Thread", true, r); + @LauncherAPI + public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections"); + + // Instance + private final LaunchServer server; + private final AtomicReference serverSocket = new AtomicReference<>(); + private final ExecutorService threadPool = Executors.newCachedThreadPool(THREAD_FACTORY); + + // API + private final Map customResponses = new ConcurrentHashMap<>(2); + private final AtomicLong idCounter = new AtomicLong(0L); + private Set sockets; + private Selector selector; + private ServerSocketChannel serverChannel; + private volatile Listener listener; + + public NettyServerSocketHandler(LaunchServer server) { + this.server = server; + } + + @Override + public void close() { + ServerSocket socket = serverSocket.getAndSet(null); + if (socket != null) { + LogHelper.info("Closing server socket listener"); + try { + socket.close(); + } catch (IOException e) { + LogHelper.error(e); + } + } + } + + public SSLContext SSLContextInit() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, KeyManagementException, IOException, CertificateException { + TrustManager[] trustAllCerts = new TrustManager[] { + new LauncherTrustManager() + }; + KeyStore ks = LauncherKeyStore.getKeyStore("keystore","PSP1000"); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory + .getDefaultAlgorithm()); + kmf.init(ks, "PSP1000".toCharArray()); + SSLContext sc = SSLContext.getInstance("TLSv1.2"); + sc.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom()); + return sc; + } + + @Override + public void run() { + SSLContext sc = null; + try { + sc = SSLContextInit(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (UnrecoverableKeyException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (CertificateException e) { + e.printStackTrace(); + } + //System.setProperty( "javax.net.ssl.keyStore","keystore"); + //System.setProperty( "javax.net.ssl.keyStorePassword","PSP1000"); + try { + selector = Selector.open(); + serverChannel = ServerSocketChannel.open(); + serverChannel.configureBlocking(false); + } catch (IOException e) { + e.printStackTrace(); + } + LogHelper.info("Starting server socket thread"); + SSLEngine engine = sc.createSSLEngine(); + engine.setUseClientMode(false); + EventLoopGroup bossGroup = new NioEventLoopGroup(); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.DEBUG)) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(NioSocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + //p.addLast(new LoggingHandler(LogLevel.INFO)); + System.out.println("P!"); + p.addLast("httpServerCodec", new HttpServerCodec()); + p.addLast(new WebSocketServerProtocolHandler("/chat")); + } + }); + ChannelFuture f = b.bind(server.config.getSocketAddress()).sync(); + f.channel().closeFuture().sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + /* + try (SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket()) { + serverSocket.setEnabledProtocols(new String[] {"TLSv1.2"}); + if (!this.serverSocket.compareAndSet(null, serverSocket)) { + throw new IllegalStateException("Previous socket wasn't closed"); + } + + // Set socket params + serverSocket.setReuseAddress(true); + serverSocket.setPerformancePreferences(1, 0, 2); + //serverSocket.setReceiveBufferSize(0x10000); + serverSocket.bind(server.config.getSocketAddress()); + LogHelper.info("Server socket thread successfully started"); + // Listen for incoming connections + while (serverSocket.isBound()) { + SSLSocket socket = (SSLSocket) serverSocket.accept(); + sockets.add(socket); + socket.startHandshake(); + // Invoke pre-connect listener + long id = idCounter.incrementAndGet(); + if (listener != null && !listener.onConnect(id, socket.getInetAddress())) { + continue; // Listener didn't accepted this connection + } + + // Reply in separate thread + threadPool.execute(new ResponseThread(server, id, socket)); + } + } catch (IOException e) { + // Ignore error after close/rebind + if (serverSocket.get() != null) { + LogHelper.error(e); + } + } + */ + } + + @LauncherAPI + public void registerCustomResponse(String name, Response.Factory factory) { + VerifyHelper.verifyIDName(name); + VerifyHelper.putIfAbsent(customResponses, name, Objects.requireNonNull(factory, "factory"), + String.format("Custom response has been already registered: '%s'", name)); + } + + @LauncherAPI + public void setListener(Listener listener) { + this.listener = listener; + } + + /*package*/ void onDisconnect(long id, Exception e) { + if (listener != null) { + listener.onDisconnect(id, e); + } + } + + /*package*/ boolean onHandshake(long id, Integer type) { + return listener == null || listener.onHandshake(id, type); + } + + public interface Listener { + @LauncherAPI + boolean onConnect(long id, InetAddress address); + + @LauncherAPI + void onDisconnect(long id, Exception e); + + @LauncherAPI + boolean onHandshake(long id, Integer type); + } +} diff --git a/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherKeyStore.java b/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherKeyStore.java new file mode 100644 index 00000000..e173d8b9 --- /dev/null +++ b/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherKeyStore.java @@ -0,0 +1,24 @@ +package ru.gravit.launcher.ssl; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +public class LauncherKeyStore { + public static KeyStore getKeyStore(String keystore,String password) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + KeyStore ks = KeyStore.getInstance("JKS"); + InputStream ksIs = new FileInputStream(keystore); + try { + ks.load(ksIs, password.toCharArray()); + } finally { + if (ksIs != null) { + ksIs.close(); + } + } + return ks; + } +} diff --git a/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherSSLContext.java b/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherSSLContext.java new file mode 100644 index 00000000..4a7e8026 --- /dev/null +++ b/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherSSLContext.java @@ -0,0 +1,33 @@ +package ru.gravit.launcher.ssl; + +import javax.net.ssl.*; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; + +public class LauncherSSLContext { + public SSLServerSocketFactory ssf; + public SSLSocketFactory sf; + private SSLContext sc; + public LauncherSSLContext(KeyStore ks,String keypassword) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, UnrecoverableKeyException, KeyManagementException { + TrustManager[] trustAllCerts = new TrustManager[] { + new LauncherTrustManager() + }; + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory + .getDefaultAlgorithm()); + kmf.init(ks, keypassword.toCharArray()); + SSLContext sc = SSLContext.getInstance("TLSv1.2"); + sc.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom()); + ssf = sc.getServerSocketFactory(); + sf = sc.getSocketFactory(); + } + public LauncherSSLContext() throws NoSuchAlgorithmException, KeyManagementException { + TrustManager[] trustAllCerts = new TrustManager[] { + new LauncherTrustManager() + }; + SSLContext sc = SSLContext.getInstance("TLSv1.2"); + sc.init(null, trustAllCerts, new SecureRandom()); + ssf = null; + sf = sc.getSocketFactory(); + } +} diff --git a/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherTrustManager.java b/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherTrustManager.java new file mode 100644 index 00000000..d1ef8262 --- /dev/null +++ b/libLauncher/src/main/java/ru/gravit/launcher/ssl/LauncherTrustManager.java @@ -0,0 +1,15 @@ +package ru.gravit.launcher.ssl; + +import javax.net.ssl.X509TrustManager; + +public class LauncherTrustManager implements X509TrustManager { + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } +}