diff --git a/pom.xml b/pom.xml index f3587aa..55ed5b2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,50 +3,34 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - com.stfl + cc.springcloud shadowsocks-java - 0.2-SNAPSHOT - + pom + 0.1-SNAPSHOT - UTF-8 + 1.8 - + + + org.slf4j + slf4j-api + 1.7.25 + + + + shadowsocks-ui + shadowsocks-core + org.apache.maven.plugins maven-compiler-plugin - 3.0 - 1.7 - 1.7 + 8 + 8 - - - - org.bouncycastle - bcprov-jdk15on - 1.52 - - - com.googlecode.json-simple - json-simple - 1.1.1 - - - - junit - junit - - - org.hamcrest - hamcrest-core - - - - diff --git a/shadowsocks-core/pom.xml b/shadowsocks-core/pom.xml new file mode 100644 index 0000000..39d6ddc --- /dev/null +++ b/shadowsocks-core/pom.xml @@ -0,0 +1,23 @@ + + + + shadowsocks-java + cc.springcloud + 0.1-SNAPSHOT + ../ + + 4.0.0 + + shadowsocks-core + 0.1-SNAPSHOT + + + + org.bouncycastle + bcprov-jdk15on + 1.52 + + + \ No newline at end of file diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/Constant.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/Constant.java new file mode 100644 index 0000000..5c9101a --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/Constant.java @@ -0,0 +1,7 @@ +package cc.springcloud.socks; + +public class Constant { + public static final String PROGRAM_NAME = "shadowSocks-java"; + public static final String VERSION = "0.2"; + public static final int BUFFER_SIZE = 16384; +} diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/Util.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/Util.java new file mode 100644 index 0000000..59d84ec --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/Util.java @@ -0,0 +1,89 @@ +package cc.springcloud.socks; + +import cc.springcloud.socks.network.proxy.Socks5Proxy; + +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; + +/** + * Created by XYUU on 2019/1/25. + */ +public class Util { + + public static byte[] randomBytes(int size) { + byte[] bytes = new byte[size]; + new SecureRandom().nextBytes(bytes); + return bytes; + } + + public static String getRequestedHostInfo(byte[] data) { + String ret = ""; + int port; + int neededLength; + switch (data[0]) { + case Socks5Proxy.ATYP_IP_V4: + // IP v4 Address + // 4 bytes of IP, 2 bytes of port + neededLength = 6; + if (data.length > neededLength) { + port = getPort(data[5], data[6]); + ret = String.format("%d.%d.%d.%d:%d", data[1], data[2], data[3], data[4], port); + } + break; + case Socks5Proxy.ATYP_DOMAIN_NAME: + // domain + neededLength = data[1]; + if (data.length > neededLength + 3) { + port = getPort(data[neededLength + 2], data[neededLength + 3]); + String domain = bytesToString(data, 2, neededLength); + ret = String.format("%s:%d", domain, port); + } + break; + case Socks5Proxy.ATYP_IP_V6: + // IP v6 Address + // 16 bytes of IP, 2 bytes of port + neededLength = 18; + if (data.length > neededLength) { + port = getPort(data[17], data[18]); + ret = String.format("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%d", + data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], + data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], + port); + } + break; + } + + return ret; + } + + public static byte[] composeSSHeader(String host, int port) { + // TYPE (1 byte) + LENGTH (1 byte) + HOST (var bytes) + PORT (2 bytes) + byte[] respData = new byte[host.length() + 4]; + + respData[0] = Socks5Proxy.ATYP_DOMAIN_NAME; + respData[1] = (byte)host.length(); + System.arraycopy(host.getBytes(), 0, respData, 2, host.length()); + respData[host.length() + 2] = (byte)(port >> 8); + respData[host.length() + 3] = (byte)(port & 0xFF); + + return respData; + } + + public static String bytesToString(byte[] data, int start, int length) { + String str = ""; + try { + str = new String(data, start, length, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return str; + } + + private static int getPort(byte b, byte b1) { + return byteToUnsignedByte(b) << 8 | byteToUnsignedByte(b1); + } + + private static short byteToUnsignedByte(byte b) { + return (short)(b & 0xff); + } +} diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/network/Config.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/Config.java new file mode 100644 index 0000000..250291c --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/Config.java @@ -0,0 +1,90 @@ +package cc.springcloud.socks.network; + +import cc.springcloud.socks.network.proxy.IProxy; +import cc.springcloud.socks.ss.AesCrypt; + +/** + * Created by XYUU on 2019/1/24. + */ +public class Config { + private String remoteIpAddress; + private String localIpAddress = "127.0.0.1"; + private int remotePort = 1080; + private int localPort = 1080; + private String proxyType = IProxy.TYPE.SOCKS5.name(); + private String method = AesCrypt.CIPHER_AES_256_CFB; + private String password; + private String logLevel = "INFO"; + + public String getRemoteIpAddress() { + return remoteIpAddress; + } + + public Config setRemoteIpAddress(String remoteIpAddress) { + this.remoteIpAddress = remoteIpAddress; + return this; + } + + public String getLocalIpAddress() { + return localIpAddress; + } + + public Config setLocalIpAddress(String localIpAddress) { + this.localIpAddress = localIpAddress; + return this; + } + + public int getRemotePort() { + return remotePort; + } + + public Config setRemotePort(int remotePort) { + this.remotePort = remotePort; + return this; + } + + public int getLocalPort() { + return localPort; + } + + public Config setLocalPort(int localPort) { + this.localPort = localPort; + return this; + } + + public String getProxyType() { + return proxyType; + } + + public Config setProxyType(String proxyType) { + this.proxyType = proxyType; + return this; + } + + public String getMethod() { + return method; + } + + public Config setMethod(String method) { + this.method = method; + return this; + } + + public String getPassword() { + return password; + } + + public Config setPassword(String password) { + this.password = password; + return this; + } + + public String getLogLevel() { + return logLevel; + } + + public Config setLogLevel(String logLevel) { + this.logLevel = logLevel; + return this; + } +} diff --git a/src/main/java/com/stfl/network/IServer.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/IServer.java similarity index 63% rename from src/main/java/com/stfl/network/IServer.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/IServer.java index 88a8104..a858582 100644 --- a/src/main/java/com/stfl/network/IServer.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/IServer.java @@ -1,4 +1,4 @@ -package com.stfl.network; +package cc.springcloud.socks.network; public interface IServer extends Runnable { void close(); diff --git a/src/main/java/com/stfl/network/LocalServer.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/LocalServer.java similarity index 85% rename from src/main/java/com/stfl/network/LocalServer.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/LocalServer.java index 0098db9..9e5dfa1 100644 --- a/src/main/java/com/stfl/network/LocalServer.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/LocalServer.java @@ -29,7 +29,13 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network; +package cc.springcloud.socks.network; + +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.network.io.PipeSocket; +import cc.springcloud.socks.ss.CryptBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.ServerSocket; @@ -39,26 +45,19 @@ import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.logging.Logger; - -import com.stfl.Constant; -import com.stfl.misc.Config; -import com.stfl.misc.Util; -import com.stfl.network.io.PipeSocket; -import com.stfl.ss.CryptFactory; /** * Blocking local server for shadowsocks */ public class LocalServer implements IServer { - private Logger logger = Logger.getLogger(LocalServer.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); private Config _config; private ServerSocket _serverSocket; private Executor _executor; private List _pipes; public LocalServer(Config config) throws IOException, InvalidAlgorithmParameterException { - if (!CryptFactory.isCipherExisted(config.getMethod())) { + if (CryptBuilder.isCipherNotExisted(config.getMethod())) { throw new InvalidAlgorithmParameterException(config.getMethod()); } _config = config; @@ -67,7 +66,7 @@ public LocalServer(Config config) throws IOException, InvalidAlgorithmParameterE _pipes = new ArrayList<>(); // print server info - logger.info("Shadowsocks-Java v" + Constant.VERSION); + logger.info("Shadowsocks-Java v{}",Constant.VERSION); logger.info(config.getProxyType() + " Proxy Server starts at port: " + config.getLocalPort()); } @@ -80,7 +79,7 @@ public void run() { _pipes.add(pipe); _executor.execute(pipe); } catch (IOException e) { - logger.warning(Util.getErrorMessage(e)); + logger.warn("io exception",e); } } } @@ -93,7 +92,7 @@ public void close() { _pipes.clear(); _serverSocket.close(); } catch (IOException e) { - logger.warning(Util.getErrorMessage(e)); + logger.warn("io exception",e); } } diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/network/NioLocalServer.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/NioLocalServer.java new file mode 100644 index 0000000..25dde9e --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/NioLocalServer.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2015, Blake + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package cc.springcloud.socks.network; + +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.network.nio.PipeWorker; +import cc.springcloud.socks.network.nio.RemoteSocketHandler; +import cc.springcloud.socks.network.nio.SocketHandlerBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.security.InvalidAlgorithmParameterException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + + +/** + * Non-blocking local server for shadowsocks + */ +public class NioLocalServer extends SocketHandlerBase { + private Logger logger = LoggerFactory.getLogger(getClass()); + + private ServerSocketChannel _serverChannel; + private RemoteSocketHandler _remoteSocketHandler; + private ExecutorService _executor; + + public NioLocalServer(Config config) throws IOException, InvalidAlgorithmParameterException { + super(config); + _executor = Executors.newCachedThreadPool(); + + // init remote socket handler + _remoteSocketHandler = new RemoteSocketHandler(_config); + _executor.execute(_remoteSocketHandler); + + // print server info + logger.info("Shadowsocks-Java v{}", Constant.VERSION); + logger.info("Cipher: {}", config.getMethod()); + logger.info("{} Proxy Server starts at port: {}", config.getProxyType(), config.getLocalPort()); + } + + @Override + protected Selector initSelector() throws IOException { + Selector socketSelector = super.initSelector(); + _serverChannel = ServerSocketChannel.open(); + _serverChannel.configureBlocking(false); + InetSocketAddress isa = new InetSocketAddress(_config.getLocalIpAddress(), _config.getLocalPort()); + _serverChannel.socket().bind(isa); + _serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT); + return socketSelector; + } + + @Override + protected void finishConnection(SelectionKey key) { + throw new UnsupportedOperationException(); + } + + @Override + protected void accept(SelectionKey key) throws IOException { + // local socket established + ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); + SocketChannel socketChannel = serverSocketChannel.accept(); + socketChannel.configureBlocking(false); + socketChannel.register(_selector, SelectionKey.OP_READ); + + // prepare local socket write queue + createWriteBuffer(socketChannel); + + // create pipe between local and remote socket + PipeWorker pipe = _remoteSocketHandler.createPipe(this, socketChannel, _config.getRemoteIpAddress(), _config.getRemotePort()); + _pipes.put(socketChannel, pipe); + _executor.execute(pipe); + } + + + + @Override + protected void read(SelectionKey key) { + super.read(key, true); + } + + @Override + public void close() { + super.close(); + _executor.shutdownNow(); + + try { + _serverChannel.close(); + _remoteSocketHandler.close(); + } catch (IOException e) { + logger.warn("io exception", e); + } + logger.info("Server closed."); + } +} diff --git a/src/main/java/com/stfl/network/io/PipeSocket.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/io/PipeSocket.java similarity index 52% rename from src/main/java/com/stfl/network/io/PipeSocket.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/io/PipeSocket.java index 6d198da..f9cc08f 100644 --- a/src/main/java/com/stfl/network/io/PipeSocket.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/io/PipeSocket.java @@ -29,28 +29,34 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.io; +package cc.springcloud.socks.network.io; -import com.stfl.misc.Config; -import com.stfl.misc.Util; -import com.stfl.Constant; -import com.stfl.network.proxy.IProxy; -import com.stfl.network.proxy.ProxyFactory; -import com.stfl.ss.CryptFactory; -import com.stfl.ss.ICrypt; -import java.io.*; +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.Util; +import cc.springcloud.socks.network.Config; +import cc.springcloud.socks.network.proxy.IProxy; +import cc.springcloud.socks.network.proxy.ProxyFactory; +import cc.springcloud.socks.ss.CryptBuilder; +import cc.springcloud.socks.ss.ICrypt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.List; import java.util.concurrent.Executor; -import java.util.logging.Logger; + /** * Pipe local and remote sockets while server is running under blocking mode. */ public class PipeSocket implements Runnable { - private Logger logger = Logger.getLogger(PipeSocket.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); private final int TIMEOUT = 10000; // 10s private ByteArrayOutputStream _remoteOutStream; @@ -68,7 +74,7 @@ public PipeSocket(Executor executor, Socket socket, Config config) throws IOExce _local = socket; _local.setSoTimeout(TIMEOUT); _config = config; - _crypt = CryptFactory.get(_config.getMethod(), _config.getPassword()); + _crypt = CryptBuilder.build(_config.getMethod(), _config.getPassword()); _proxy = ProxyFactory.get(_config.getProxyType()); _remoteOutStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE); _localOutStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE); @@ -81,7 +87,7 @@ public void run() { _remote.setSoTimeout(TIMEOUT); } catch (IOException e) { close(); - logger.warning(Util.getErrorMessage(e)); + logger.warn("pipe socket run error",e); return; } @@ -94,115 +100,108 @@ private Socket initRemote(Config config) throws IOException { } private Runnable getLocalWorker() { - return new Runnable() { - @Override - public void run() { - BufferedInputStream stream; - byte[] dataBuffer = new byte[Constant.BUFFER_SIZE]; - byte[] buffer; - int readCount; - List sendData = null; - - // prepare local stream + return () -> { + BufferedInputStream stream; + byte[] dataBuffer = new byte[Constant.BUFFER_SIZE]; + byte[] buffer; + int readCount; + List sendData = null; + + // prepare local stream + try { + stream = new BufferedInputStream(_local.getInputStream()); + } catch (IOException e) { + logger.info(e.toString()); + return; + } + + // start to process data from local socket + while (true) { try { - stream = new BufferedInputStream(_local.getInputStream()); - } catch (IOException e) { - logger.info(e.toString()); - return; - } + // read data + readCount = stream.read(dataBuffer); + if (readCount == -1) { + throw new IOException("Local socket closed (Read)!"); + } - // start to process data from local socket - while (true) { - try { - // read data - readCount = stream.read(dataBuffer); - if (readCount == -1) { - throw new IOException("Local socket closed (Read)!"); - } + // initialize proxy + if (!_proxy.isReady()) { + byte[] temp; + buffer = new byte[readCount]; + + // dup dataBuffer to use in later + System.arraycopy(dataBuffer, 0, buffer, 0, readCount); - // initialize proxy - if (!_proxy.isReady()) { - byte[] temp; - buffer = new byte[readCount]; - - // dup dataBuffer to use in later - System.arraycopy(dataBuffer, 0, buffer, 0, readCount); - - temp = _proxy.getResponse(buffer); - if ((temp != null) && (!_sendLocal(temp, temp.length))) { - throw new IOException("Local socket closed (proxy-Write)!"); - } - // packet for remote socket - sendData = _proxy.getRemoteResponse(buffer); - if (sendData == null) { - continue; - } - logger.info("Connected to: " + Util.getRequestedHostInfo(sendData.get(0))); + temp = _proxy.getResponse(buffer); + if ((temp != null) && (!_sendLocal(temp, temp.length))) { + throw new IOException("Local socket closed (proxy-Write)!"); } - else { - sendData.clear(); - sendData.add(dataBuffer); + // packet for remote socket + sendData = _proxy.getRemoteResponse(buffer); + if (sendData == null) { + continue; } + logger.info("Connected to: {}",Util.getRequestedHostInfo(sendData.get(0))); + } + else { + assert sendData != null; + sendData.clear(); + sendData.add(dataBuffer); + } - for (byte[] bytes : sendData) { - // send data to remote socket - if (!sendRemote(bytes, bytes.length)) { - throw new IOException("Remote socket closed (Write)!"); - } + for (byte[] bytes : sendData) { + // send data to remote socket + if (!sendRemote(bytes, bytes.length)) { + throw new IOException("Remote socket closed (Write)!"); } - } catch (SocketTimeoutException e) { - continue; - } catch (IOException e) { - logger.fine(e.toString()); - break; } + } catch (SocketTimeoutException ignored) { + } catch (IOException e) { + logger.info("io error",e); + break; } - close(); - logger.fine(String.format("localWorker exit, Local=%s, Remote=%s", _local, _remote)); } + close(); + logger.info(String.format("localWorker exit, Local=%s, Remote=%s", _local, _remote)); }; } private Runnable getRemoteWorker() { - return new Runnable() { - @Override - public void run() { - BufferedInputStream stream; - int readCount; - byte[] dataBuffer = new byte[4096]; - - // prepare remote stream - try { - //stream = _remote.getInputStream(); - stream = new BufferedInputStream (_remote.getInputStream()); - } catch (IOException e) { - logger.info(e.toString()); - return; - } + return () -> { + BufferedInputStream stream; + int readCount; + byte[] dataBuffer = new byte[4096]; - // start to process data from remote socket - while (true) { - try { - readCount = stream.read(dataBuffer); - if (readCount == -1) { - throw new IOException("Remote socket closed (Read)!"); - } + // prepare remote stream + try { + //stream = _remote.getInputStream(); + stream = new BufferedInputStream (_remote.getInputStream()); + } catch (IOException e) { + logger.info(e.toString()); + return; + } - // send data to local socket - if (!sendLocal(dataBuffer, readCount)) { - throw new IOException("Local socket closed (Write)!"); - } - } catch (SocketTimeoutException e) { - continue; - } catch (IOException e) { - logger.fine(e.toString()); - break; + // start to process data from remote socket + while (true) { + try { + readCount = stream.read(dataBuffer); + if (readCount == -1) { + throw new IOException("Remote socket closed (Read)!"); } + // send data to local socket + if (!sendLocal(dataBuffer, readCount)) { + throw new IOException("Local socket closed (Write)!"); + } + } catch (SocketTimeoutException ignored) { + } catch (IOException e) { + logger.info("io error",e); + break; } - close(); - logger.fine(String.format("remoteWorker exit, Local=%s, Remote=%s", _local, _remote)); + } + close(); + logger.info("remoteWorker exit, Local={}, Remote={}", _local, _remote); }; } @@ -217,7 +216,7 @@ public void close() { _local.shutdownOutput(); _local.close(); } catch (IOException e) { - logger.fine("PipeSocket failed to close local socket (I/O exception)!"); + logger.info("PipeSocket failed to close local socket (I/O exception)!",e); } try { if (_remote != null) { @@ -226,7 +225,7 @@ public void close() { _remote.close(); } } catch (IOException e) { - logger.fine("PipeSocket failed to close remote socket (I/O exception)!"); + logger.info("PipeSocket failed to close remote socket (I/O exception)!",e); } } @@ -247,7 +246,7 @@ private boolean _sendRemote(byte[] data, int length) { logger.info("Nothing to sendRemote!\n"); } } catch (IOException e) { - logger.info(Util.getErrorMessage(e)); + logger.info("send remote error",e); return false; } @@ -266,7 +265,7 @@ private boolean _sendLocal(byte[] data, int length) { OutputStream outStream = _local.getOutputStream(); outStream.write(data, 0, length); } catch (IOException e) { - logger.info(Util.getErrorMessage(e)); + logger.info("send local error",e); return false; } return true; diff --git a/src/main/java/com/stfl/network/nio/ChangeRequest.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/ChangeRequest.java similarity index 98% rename from src/main/java/com/stfl/network/nio/ChangeRequest.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/ChangeRequest.java index 5bdc094..ac1ef6c 100644 --- a/src/main/java/com/stfl/network/nio/ChangeRequest.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/ChangeRequest.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.nio; +package cc.springcloud.socks.network.nio; import java.nio.channels.SocketChannel; diff --git a/src/main/java/com/stfl/network/nio/ISocketHandler.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/ISocketHandler.java similarity index 97% rename from src/main/java/com/stfl/network/nio/ISocketHandler.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/ISocketHandler.java index 3b504ba..3b6be7f 100644 --- a/src/main/java/com/stfl/network/nio/ISocketHandler.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/ISocketHandler.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.nio; +package cc.springcloud.socks.network.nio; /** * Interface of socket handler diff --git a/src/main/java/com/stfl/network/nio/PipeEvent.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/PipeEvent.java similarity index 97% rename from src/main/java/com/stfl/network/nio/PipeEvent.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/PipeEvent.java index 105f595..cde61d7 100644 --- a/src/main/java/com/stfl/network/nio/PipeEvent.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/PipeEvent.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.nio; +package cc.springcloud.socks.network.nio; /** * pipe event for pipe worker diff --git a/src/main/java/com/stfl/network/nio/PipeWorker.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/PipeWorker.java similarity index 84% rename from src/main/java/com/stfl/network/nio/PipeWorker.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/PipeWorker.java index 3940414..f5f80f1 100644 --- a/src/main/java/com/stfl/network/nio/PipeWorker.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/PipeWorker.java @@ -29,15 +29,17 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.nio; - -import com.stfl.misc.Config; -import com.stfl.misc.Util; -import com.stfl.Constant; -import com.stfl.network.proxy.IProxy; -import com.stfl.network.proxy.ProxyFactory; -import com.stfl.ss.CryptFactory; -import com.stfl.ss.ICrypt; +package cc.springcloud.socks.network.nio; + +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.Util; +import cc.springcloud.socks.network.Config; +import cc.springcloud.socks.network.proxy.IProxy; +import cc.springcloud.socks.network.proxy.ProxyFactory; +import cc.springcloud.socks.ss.CryptBuilder; +import cc.springcloud.socks.ss.ICrypt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -46,11 +48,10 @@ import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import java.util.logging.Logger; public class PipeWorker implements Runnable { - private Logger logger = Logger.getLogger(PipeWorker.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); private SocketChannel _localChannel; private SocketChannel _remoteChannel; private ISocketHandler _localSocketHandler; @@ -59,7 +60,7 @@ public class PipeWorker implements Runnable { private ICrypt _crypt; public String socketInfo; private ByteArrayOutputStream _outStream; - private BlockingQueue _processQueue; + private BlockingQueue _processQueue; private volatile boolean requestedClose; public PipeWorker(ISocketHandler localHandler, SocketChannel localChannel, ISocketHandler remoteHandler, SocketChannel remoteChannel, Config config) { @@ -67,10 +68,10 @@ public PipeWorker(ISocketHandler localHandler, SocketChannel localChannel, ISock _remoteChannel = remoteChannel; _localSocketHandler = localHandler; _remoteSocketHandler = remoteHandler; - _crypt = CryptFactory.get(config.getMethod(), config.getPassword()); + _crypt = CryptBuilder.build(config.getMethod(), config.getPassword()); _proxy = ProxyFactory.get(config.getProxyType()); _outStream = new ByteArrayOutputStream(Constant.BUFFER_SIZE); - _processQueue = new LinkedBlockingQueue(); + _processQueue = new LinkedBlockingQueue<>(); requestedClose = false; socketInfo = String.format("Local: %s, Remote: %s", localChannel, remoteChannel); } @@ -81,7 +82,7 @@ public void close() { } public void forceClose() { - logger.fine("PipeWorker::forceClose " + socketInfo); + logger.info("PipeWorker::forceClose {}", socketInfo); // close socket now! try { @@ -92,7 +93,7 @@ public void forceClose() { _remoteChannel.close(); } } catch (IOException e) { - logger.fine("PipeWorker::forceClose> " + e.toString()); + logger.info("PipeWorker::forceClose> {}", e); } // follow the standard close steps @@ -104,8 +105,7 @@ public void processData(byte[] data, int count, boolean isEncrypted) { byte[] dataCopy = new byte[count]; System.arraycopy(data, 0, dataCopy, 0, count); _processQueue.add(new PipeEvent(dataCopy, isEncrypted)); - } - else { + } else { _processQueue.add(new PipeEvent()); } } @@ -117,10 +117,10 @@ public void run() { SocketChannel channel; List sendData = null; - while(true) { + while (true) { // make sure all the requests in the queue are processed if (_processQueue.isEmpty() && requestedClose) { - logger.fine("PipeWorker closed ("+ _processQueue.size() + "): " + this.socketInfo); + logger.info("PipeWorker closed ({}): {}", _processQueue.size(), this.socketInfo); if (_localChannel.isOpen()) { _localSocketHandler.send(new ChangeRequest(_localChannel, ChangeRequest.CLOSE_CHANNEL)); } @@ -131,7 +131,7 @@ public void run() { } try { - event = (PipeEvent)_processQueue.take(); + event = _processQueue.take(); // if event data is null, it means this is a wake-up call // to check if any other thread is requested to close sockets @@ -153,10 +153,10 @@ public void run() { continue; } // index 0 is always ss payload - logger.info("Connected to: " + Util.getRequestedHostInfo(sendData.get(0))); + logger.info("Connected to: {}", Util.getRequestedHostInfo(sendData.get(0))); //logger.info("Test: " + Util.bytesToString(temp, 0, temp.length)); - } - else { + } else { + assert sendData != null; sendData.clear(); sendData.add(event.data); } @@ -179,8 +179,9 @@ public void run() { ChangeRequest request = new ChangeRequest(channel, ChangeRequest.CHANGE_SOCKET_OP, SelectionKey.OP_WRITE); socketHandler.send(request, _outStream.toByteArray()); } + } catch (InterruptedException e) { - logger.fine(Util.getErrorMessage(e)); + logger.info("Interrupted Exception", e); break; } } diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/RemoteSocketHandler.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/RemoteSocketHandler.java new file mode 100644 index 0000000..0afe5ee --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/RemoteSocketHandler.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, Blake + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package cc.springcloud.socks.network.nio; + + +import cc.springcloud.socks.network.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.security.InvalidAlgorithmParameterException; + +/** + * Handler for processing all IO event for remote sockets + */ +public class RemoteSocketHandler extends SocketHandlerBase { + private Logger logger = LoggerFactory.getLogger(getClass()); + + public RemoteSocketHandler(Config config) throws IOException, InvalidAlgorithmParameterException { + super(config); + } + + @Override + protected Selector initSelector() throws IOException { + return SelectorProvider.provider().openSelector(); + } + + @Override + protected void finishConnection(SelectionKey key) { + SocketChannel socketChannel = (SocketChannel) key.channel(); + try { + socketChannel.finishConnect(); + } catch (IOException e) { + logger.warn("RemoteSocketHandler::finishConnection I/O exception: {}",e.toString()); + cleanUp(socketChannel); + return; + } + key.interestOps(SelectionKey.OP_WRITE); + } + + + @Override + protected void accept(SelectionKey key){ + throw new UnsupportedOperationException(); + } + + @Override + protected void read(SelectionKey key) { + super.read(key, false); + } + + + @Override + protected boolean processPendingRequest(ChangeRequest request) { + if ((request.type != ChangeRequest.REGISTER_CHANNEL) && request.socket.isConnectionPending()) { + return false; + } + return super.processPendingRequest(request); + } + + public PipeWorker createPipe(ISocketHandler localHandler, SocketChannel localChannel, String ipAddress, int port) throws IOException { + // prepare remote socket + SocketChannel socketChannel = SocketChannel.open(); + socketChannel.configureBlocking(false); + socketChannel.connect(new InetSocketAddress(ipAddress, port)); + // create write buffer for specified socket + createWriteBuffer(socketChannel); + // create pipe worker for handling encrypt and decrypt + PipeWorker pipe = new PipeWorker(localHandler, localChannel, this, socketChannel, _config); + // setup pipe info + //pipe.setRemoteChannel(socketChannel); + _pipes.put(socketChannel, pipe); + synchronized(_pendingRequest) { + _pendingRequest.add(new ChangeRequest(socketChannel, ChangeRequest.REGISTER_CHANNEL, SelectionKey.OP_CONNECT)); + } + return pipe; + } + +} diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/SocketHandlerBase.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/SocketHandlerBase.java new file mode 100644 index 0000000..0422141 --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/nio/SocketHandlerBase.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2015, Blake + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package cc.springcloud.socks.network.nio; + +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.network.Config; +import cc.springcloud.socks.network.IServer; +import cc.springcloud.socks.ss.CryptBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.nio.channels.spi.SelectorProvider; +import java.security.InvalidAlgorithmParameterException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Base class of socket handler for processing all IO event for sockets + */ +public abstract class SocketHandlerBase implements IServer, ISocketHandler { + private Logger logger = LoggerFactory.getLogger(getClass()); + protected Selector _selector; + protected Config _config; + protected final List _pendingRequest = new LinkedList<>(); + protected final ConcurrentHashMap> _pendingData = new ConcurrentHashMap<>(); + protected ConcurrentMap _pipes = new ConcurrentHashMap<>(); + protected ByteBuffer _readBuffer = ByteBuffer.allocate(Constant.BUFFER_SIZE); + + protected Selector initSelector() throws IOException { + return SelectorProvider.provider().openSelector(); + } + + protected boolean processPendingRequest(ChangeRequest request) { + switch (request.type) { + case ChangeRequest.CHANGE_SOCKET_OP: + SelectionKey key = request.socket.keyFor(_selector); + if ((key != null) && key.isValid()) { + key.interestOps(request.op); + } else { + logger.warn("processPendingRequest (drop): {} {}", key, request.socket); + } + break; + case ChangeRequest.REGISTER_CHANNEL: + try { + request.socket.register(_selector, request.op); + } catch (ClosedChannelException e) { + // socket get closed by remote + logger.warn("socket channel closed", e); + cleanUp(request.socket); + } + break; + case ChangeRequest.CLOSE_CHANNEL: + cleanUp(request.socket); + break; + } + return true; + } + + protected abstract void finishConnection(SelectionKey key); + + protected abstract void accept(SelectionKey key) throws IOException; + + protected abstract void read(SelectionKey key); + + protected void processSelect(SelectionKey key) { + // Handle event + try { + if (key.isValid()) { + if (key.isConnectable()) { + finishConnection(key); + } else if (key.isAcceptable()) { + accept(key); + } else if (key.isReadable()) { + read(key); + } else if (key.isWritable()) { + write(key); + } + } + } catch (IOException e) { + cleanUp((SocketChannel) key.channel()); + } + } + + public SocketHandlerBase(Config config) throws IOException, InvalidAlgorithmParameterException { + if (CryptBuilder.isCipherNotExisted(config.getMethod())) { + throw new InvalidAlgorithmParameterException(config.getMethod()); + } + _config = config; + _selector = initSelector(); + } + + @Override + public void run() { + while (true) { + try { + synchronized (_pendingRequest) { + Iterator changes = _pendingRequest.iterator(); + while (changes.hasNext()) { + ChangeRequest change = (ChangeRequest) changes.next(); + if (!processPendingRequest(change)) + break; + changes.remove(); + } + } + + // wait events from selected channels + _selector.select(); + + Iterator selectedKeys = _selector.selectedKeys().iterator(); + while (selectedKeys.hasNext()) { + SelectionKey key = (SelectionKey) selectedKeys.next(); + selectedKeys.remove(); + + if (!key.isValid()) { + continue; + } + + processSelect(key); + } + } catch (ClosedSelectorException e) { + break; + } catch (Exception e) { + logger.warn("run exception", e); + } + } + logger.info("{} Closed.", getClass()); + } + + protected void createWriteBuffer(SocketChannel socketChannel) { + List queue = new ArrayList<>(); + Object put; + put = _pendingData.putIfAbsent(socketChannel, queue); + if (put != null) { + logger.info("Dup write buffer creation: {}", socketChannel); + } + } + + protected void cleanUp(SocketChannel socketChannel) { + try { + socketChannel.close(); + } catch (IOException e) { + logger.info("io exception", e); + } + SelectionKey key = socketChannel.keyFor(_selector); + if (key != null) { + key.cancel(); + } + _pendingData.remove(socketChannel); + + PipeWorker pipe = _pipes.get(socketChannel); + if (pipe != null) { + pipe.close(); + _pipes.remove(socketChannel); + logger.debug("Socket closed: {}", pipe.socketInfo); + } else { + logger.debug("Socket closed (NULL): {}", socketChannel); + } + } + + @Override + public void send(ChangeRequest request, byte[] data) { + switch (request.type) { + case ChangeRequest.CHANGE_SOCKET_OP: + List queue = _pendingData.get(request.socket); + if (queue != null) { + // may be synchronized (_pendingData) + // in general case, the write queue is always existed, unless, the socket has been shutdown + queue.add(ByteBuffer.wrap(data)); + } else { + logger.warn("Socket is closed! dropping this request"); + } + break; + } + + synchronized (_pendingRequest) { + _pendingRequest.add(request); + } + + _selector.wakeup(); + } + + @Override + public void send(ChangeRequest request) { + send(request, null); + } + + public void close() { + for (PipeWorker p : _pipes.values()) { + p.forceClose(); + } + _pipes.clear(); + try { + _selector.close(); + } catch (IOException e) { + logger.warn("io error", e); + } + } + + protected void read(SelectionKey key, boolean isEncrypted) { + SocketChannel socketChannel = (SocketChannel) key.channel(); + PipeWorker pipe = _pipes.get(socketChannel); + if (pipe == null) { + // should not happen + cleanUp(socketChannel); + return; + } + _readBuffer.clear(); + int readCount; + try { + readCount = socketChannel.read(_readBuffer); + } catch (IOException e) { + cleanUp(socketChannel); + return; + } + if (readCount == -1) { + cleanUp(socketChannel); + return; + } + pipe.processData(_readBuffer.array(), readCount, isEncrypted); + } + + + private void write(SelectionKey key) throws IOException { + SocketChannel socketChannel = (SocketChannel) key.channel(); + List queue = _pendingData.get(socketChannel); + if (queue != null) { + // may be synchronized (queue) + // write data to socket + while (!queue.isEmpty()) { + ByteBuffer buf = queue.get(0); + socketChannel.write(buf); + if (buf.remaining() > 0) { + break; + } + queue.remove(0); + } + if (queue.isEmpty()) { + key.interestOps(SelectionKey.OP_READ); + } + } else { + logger.warn("Socket::write queue = null: {}", socketChannel); + } + } +} diff --git a/src/main/java/com/stfl/network/proxy/AutoProxy.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/AutoProxy.java similarity index 85% rename from src/main/java/com/stfl/network/proxy/AutoProxy.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/AutoProxy.java index 93584a2..558b9fb 100644 --- a/src/main/java/com/stfl/network/proxy/AutoProxy.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/AutoProxy.java @@ -1,9 +1,8 @@ -package com.stfl.network.proxy; - -import com.stfl.misc.Reflection; +package cc.springcloud.socks.network.proxy; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import java.util.logging.Logger; /** @@ -54,13 +53,10 @@ public boolean isMine(byte[] data) { } private void init(byte[] data) { - Object obj; IProxy proxy; - for (Map.Entry entry : ProxyFactory.proxies.entrySet()) { + for (Map.Entry> entry : ProxyFactory.proxies.entrySet()) { if (entry.getKey() == this.getType()) continue; - - obj = Reflection.get(entry.getValue()); - proxy = (IProxy)obj; + proxy = entry.getValue().get(); if (proxy.isMine(data)) { logger.fine("ProxyType (Auto): " + proxy.getType()); _proxy = proxy; diff --git a/src/main/java/com/stfl/network/proxy/HttpProxy.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/HttpProxy.java similarity index 86% rename from src/main/java/com/stfl/network/proxy/HttpProxy.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/HttpProxy.java index 9436b46..d28c42c 100644 --- a/src/main/java/com/stfl/network/proxy/HttpProxy.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/HttpProxy.java @@ -29,16 +29,18 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.proxy; +package cc.springcloud.socks.network.proxy; -import com.stfl.Constant; -import com.stfl.misc.Util; + +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,9 +49,9 @@ */ public class HttpProxy implements IProxy { private static final String[] HTTP_METHODS = - new String[] {"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}; + new String[]{"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}; - private Logger logger = Logger.getLogger(HttpProxy.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); private boolean _isReady; private boolean _isHttpConnect; private Map methodCache; @@ -75,8 +77,7 @@ public byte[] getResponse(byte[] data) { if (_isHttpConnect) return String.format("HTTP/1.0 200\r\nProxy-agent: %s/%s\r\n\r\n", - Constant.PROG_NAME, Constant.VERSION).getBytes(); - + Constant.PROGRAM_NAME, Constant.VERSION).getBytes(); return null; } @@ -132,15 +133,14 @@ private Map getHttpMethod(byte[] data) { Pattern pattern = Pattern.compile("^([a-zA-Z]*) [htps]{0,4}[:/]{0,3}(\\S[^/]*)(\\S*) (\\S*)"); Map header = new HashMap<>(); if (httpHeaders.length > 0) { - logger.fine("HTTP Header: " + httpHeaders[0]); + logger.info("HTTP Header: {}", httpHeaders[0]); Matcher matcher = pattern.matcher(httpHeaders[0]); if (matcher.find()) { header.put("method", matcher.group(1)); if (matcher.group(2).startsWith("/")) { header.put("url", "/"); isHostFound = false; - } - else { + } else { header.put("host", matcher.group(2)); header.put("url", matcher.group(3)); } @@ -177,32 +177,26 @@ private byte[] reconstructHttpHeader(Map method, byte[] data) { sb.append("\r\n"); sb.append("User-Agent: test/0.1\r\n"); break; - } - else if (isFirstLine) { + } else if (isFirstLine) { sb.append(method.get("method")); sb.append(" "); sb.append(method.get("url")); sb.append(" "); sb.append(method.get("version")); isFirstLine = false; - } - else if (line.toLowerCase().contains("cache-control")) { + } else if (line.toLowerCase().contains("cache-control")) { sb.append("Pragma: no-cache\r\n"); sb.append("Cache-Control: no-cache"); - } - else if (line.toLowerCase().contains("proxy-connection")) { + } else if (line.toLowerCase().contains("proxy-connection")) { //Proxy-Connection String[] fields = line.split(":"); sb.append("Connection: "); sb.append(fields[1].trim()); - } - else if (line.toLowerCase().contains("if-none-match")) { + } else if (line.toLowerCase().contains("if-none-match")) { continue; - } - else if (line.toLowerCase().contains("if-modified-since")) { + } else if (line.toLowerCase().contains("if-modified-since")) { continue; - } - else { + } else { sb.append(line); } sb.append("\r\n"); @@ -215,14 +209,8 @@ else if (line.toLowerCase().contains("if-modified-since")) { private void setHttpMethod(Map header) { String method = header.get("method"); - if (method != null) { - if (method.toUpperCase().equals("CONNECT")) { - _isHttpConnect = true; - } - else { - _isHttpConnect = false; - } + _isHttpConnect = method.toUpperCase().equals("CONNECT"); } } diff --git a/src/main/java/com/stfl/network/proxy/IProxy.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/IProxy.java similarity index 97% rename from src/main/java/com/stfl/network/proxy/IProxy.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/IProxy.java index ab94378..44e32a2 100644 --- a/src/main/java/com/stfl/network/proxy/IProxy.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/IProxy.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.proxy; +package cc.springcloud.socks.network.proxy; import java.util.List; diff --git a/src/main/java/com/stfl/network/proxy/ProxyFactory.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/ProxyFactory.java similarity index 66% rename from src/main/java/com/stfl/network/proxy/ProxyFactory.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/ProxyFactory.java index fc8d834..4609389 100644 --- a/src/main/java/com/stfl/network/proxy/ProxyFactory.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/ProxyFactory.java @@ -29,42 +29,29 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.proxy; +package cc.springcloud.socks.network.proxy; + -import com.stfl.misc.Reflection; import java.util.*; -import java.util.logging.Logger; +import java.util.function.Supplier; /** * Proxy factory */ public class ProxyFactory { - public static final Map proxies = new HashMap() {{ - put(IProxy.TYPE.HTTP, HttpProxy.class.getName()); - put(IProxy.TYPE.SOCKS5, Socks5Proxy.class.getName()); - put(IProxy.TYPE.AUTO, AutoProxy.class.getName()); + public static final Map> proxies = new HashMap>() {{ + put(IProxy.TYPE.HTTP, HttpProxy::new); + put(IProxy.TYPE.SOCKS5, Socks5Proxy::new); + put(IProxy.TYPE.AUTO, AutoProxy::new); }}; - private static Logger logger = Logger.getLogger(ProxyFactory.class.getName()); - public static boolean isProxyTypeExisted(String name) { + public static IProxy get(String name) { IProxy.TYPE type = IProxy.TYPE.valueOf(name); - return (proxies.get(type) != null); - } - - public static IProxy get(IProxy.TYPE type) { - try { - Object obj = Reflection.get(proxies.get(type)); - return (IProxy)obj; - - } catch (Exception e) { - logger.info(com.stfl.misc.Util.getErrorMessage(e)); - } - - return null; + return proxies.get(type).get(); } public static List getSupportedProxyTypes() { - List sortedKeys = new ArrayList<>(proxies.keySet()); + List sortedKeys = new ArrayList<>(proxies.keySet()); Collections.sort(sortedKeys); return sortedKeys; } diff --git a/src/main/java/com/stfl/network/proxy/Socks5Proxy.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/Socks5Proxy.java similarity index 94% rename from src/main/java/com/stfl/network/proxy/Socks5Proxy.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/Socks5Proxy.java index 657a60c..549128a 100644 --- a/src/main/java/com/stfl/network/proxy/Socks5Proxy.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/network/proxy/Socks5Proxy.java @@ -29,11 +29,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.network.proxy; +package cc.springcloud.socks.network.proxy; import java.util.ArrayList; import java.util.List; -import java.util.logging.Logger; /** * Provide local socks5 statue and required response @@ -43,7 +42,6 @@ public class Socks5Proxy implements IProxy { public final static int ATYP_DOMAIN_NAME = 0x3; public final static int ATYP_IP_V6 = 0x4; - private Logger logger = Logger.getLogger(Socks5Proxy.class.getName()); private enum STAGE {SOCK5_HELLO, SOCKS_ACK, SOCKS_READY} private STAGE _stage; @@ -113,10 +111,6 @@ public List getRemoteResponse(byte[] data) { @Override public boolean isMine(byte[] data) { - if (data[0] == 0x5) { - return true; - } - - return false; + return data[0] == 0x5; } } diff --git a/src/main/java/com/stfl/ss/AesCrypt.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/AesCrypt.java similarity index 67% rename from src/main/java/com/stfl/ss/AesCrypt.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/AesCrypt.java index 2923a66..4b20404 100644 --- a/src/main/java/com/stfl/ss/AesCrypt.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/AesCrypt.java @@ -29,10 +29,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; import org.bouncycastle.crypto.StreamBlockCipher; -import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.engines.AESFastEngine; import org.bouncycastle.crypto.modes.CFBBlockCipher; import org.bouncycastle.crypto.modes.OFBBlockCipher; @@ -56,15 +55,15 @@ public class AesCrypt extends CryptBase { public final static String CIPHER_AES_192_OFB = "aes-192-ofb"; public final static String CIPHER_AES_256_OFB = "aes-256-ofb"; - public static Map getCiphers() { - Map ciphers = new HashMap<>(); - ciphers.put(CIPHER_AES_128_CFB, AesCrypt.class.getName()); - ciphers.put(CIPHER_AES_192_CFB, AesCrypt.class.getName()); - ciphers.put(CIPHER_AES_256_CFB, AesCrypt.class.getName()); - ciphers.put(CIPHER_AES_128_OFB, AesCrypt.class.getName()); - ciphers.put(CIPHER_AES_192_OFB, AesCrypt.class.getName()); - ciphers.put(CIPHER_AES_256_OFB, AesCrypt.class.getName()); - + public static Map getCiphers() { + Map ciphers = new HashMap<>(); + ICryptFactory factory = AesCrypt::new; + ciphers.put(CIPHER_AES_128_CFB, factory); + ciphers.put(CIPHER_AES_192_CFB, factory); + ciphers.put(CIPHER_AES_256_CFB, factory); + ciphers.put(CIPHER_AES_128_OFB, factory); + ciphers.put(CIPHER_AES_192_OFB, factory); + ciphers.put(CIPHER_AES_256_OFB, factory); return ciphers; } @@ -74,16 +73,17 @@ public AesCrypt(String name, String password) { @Override public int getKeyLength() { - if(_name.equals(CIPHER_AES_128_CFB) || _name.equals(CIPHER_AES_128_OFB)) { - return 16; - } - else if (_name.equals(CIPHER_AES_192_CFB) || _name.equals(CIPHER_AES_192_OFB)) { - return 24; - } - else if (_name.equals(CIPHER_AES_256_CFB) || _name.equals(CIPHER_AES_256_OFB)) { - return 32; + switch (_name) { + case CIPHER_AES_128_CFB: + case CIPHER_AES_128_OFB: + return 16; + case CIPHER_AES_192_CFB: + case CIPHER_AES_192_OFB: + return 24; + case CIPHER_AES_256_CFB: + case CIPHER_AES_256_OFB: + return 32; } - return 0; } @@ -92,28 +92,28 @@ protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorit AESFastEngine engine = new AESFastEngine(); StreamBlockCipher cipher; - if (_name.equals(CIPHER_AES_128_CFB)) { - cipher = new CFBBlockCipher(engine, getIVLength() * 8); - } - else if (_name.equals(CIPHER_AES_192_CFB)) { - cipher = new CFBBlockCipher(engine, getIVLength() * 8); - } - else if (_name.equals(CIPHER_AES_256_CFB)) { - cipher = new CFBBlockCipher(engine, getIVLength() * 8); + switch (_name) { + case CIPHER_AES_128_CFB: + cipher = new CFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_AES_192_CFB: + cipher = new CFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_AES_256_CFB: + cipher = new CFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_AES_128_OFB: + cipher = new OFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_AES_192_OFB: + cipher = new OFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_AES_256_OFB: + cipher = new OFBBlockCipher(engine, getIVLength() * 8); + break; + default: + throw new InvalidAlgorithmParameterException(_name); } - else if (_name.equals(CIPHER_AES_128_OFB)) { - cipher = new OFBBlockCipher(engine, getIVLength() * 8); - } - else if (_name.equals(CIPHER_AES_192_OFB)) { - cipher = new OFBBlockCipher(engine, getIVLength() * 8); - } - else if (_name.equals(CIPHER_AES_256_OFB)) { - cipher = new OFBBlockCipher(engine, getIVLength() * 8); - } - else { - throw new InvalidAlgorithmParameterException(_name); - } - return cipher; } diff --git a/src/main/java/com/stfl/ss/BlowFishCrypt.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/BlowFishCrypt.java similarity index 94% rename from src/main/java/com/stfl/ss/BlowFishCrypt.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/BlowFishCrypt.java index 85f9a47..9899416 100644 --- a/src/main/java/com/stfl/ss/BlowFishCrypt.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/BlowFishCrypt.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; import org.bouncycastle.crypto.StreamBlockCipher; import org.bouncycastle.crypto.engines.BlowfishEngine; @@ -49,10 +49,9 @@ public class BlowFishCrypt extends CryptBase { public final static String CIPHER_BLOWFISH_CFB = "bf-cfb"; - public static Map getCiphers() { - Map ciphers = new HashMap<>(); - ciphers.put(CIPHER_BLOWFISH_CFB, BlowFishCrypt.class.getName()); - + public static Map getCiphers() { + Map ciphers = new HashMap<>(); + ciphers.put(CIPHER_BLOWFISH_CFB, BlowFishCrypt::new); return ciphers; } diff --git a/src/main/java/com/stfl/ss/CamelliaCrypt.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CamelliaCrypt.java similarity index 75% rename from src/main/java/com/stfl/ss/CamelliaCrypt.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CamelliaCrypt.java index b2faac1..7b877d2 100644 --- a/src/main/java/com/stfl/ss/CamelliaCrypt.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CamelliaCrypt.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; import org.bouncycastle.crypto.StreamBlockCipher; import org.bouncycastle.crypto.engines.CamelliaEngine; @@ -51,12 +51,12 @@ public class CamelliaCrypt extends CryptBase { public final static String CIPHER_CAMELLIA_192_CFB = "camellia-192-cfb"; public final static String CIPHER_CAMELLIA_256_CFB = "camellia-256-cfb"; - public static Map getCiphers() { - Map ciphers = new HashMap<>(); - ciphers.put(CIPHER_CAMELLIA_128_CFB, CamelliaCrypt.class.getName()); - ciphers.put(CIPHER_CAMELLIA_192_CFB, CamelliaCrypt.class.getName()); - ciphers.put(CIPHER_CAMELLIA_256_CFB, CamelliaCrypt.class.getName()); - + public static Map getCiphers() { + Map ciphers = new HashMap<>(); + ICryptFactory factory = CamelliaCrypt::new; + ciphers.put(CIPHER_CAMELLIA_128_CFB, factory); + ciphers.put(CIPHER_CAMELLIA_192_CFB, factory); + ciphers.put(CIPHER_CAMELLIA_256_CFB, factory); return ciphers; } @@ -66,16 +66,14 @@ public CamelliaCrypt(String name, String password) { @Override public int getKeyLength() { - if(_name.equals(CIPHER_CAMELLIA_128_CFB)) { - return 16; - } - else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) { - return 24; + switch (_name) { + case CIPHER_CAMELLIA_128_CFB: + return 16; + case CIPHER_CAMELLIA_192_CFB: + return 24; + case CIPHER_CAMELLIA_256_CFB: + return 32; } - else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) { - return 32; - } - return 0; } @@ -84,17 +82,18 @@ protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorit CamelliaEngine engine = new CamelliaEngine(); StreamBlockCipher cipher; - if (_name.equals(CIPHER_CAMELLIA_128_CFB)) { - cipher = new CFBBlockCipher(engine, getIVLength() * 8); - } - else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) { - cipher = new CFBBlockCipher(engine, getIVLength() * 8); - } - else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) { - cipher = new CFBBlockCipher(engine, getIVLength() * 8); - } - else { - throw new InvalidAlgorithmParameterException(_name); + switch (_name) { + case CIPHER_CAMELLIA_128_CFB: + cipher = new CFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_CAMELLIA_192_CFB: + cipher = new CFBBlockCipher(engine, getIVLength() * 8); + break; + case CIPHER_CAMELLIA_256_CFB: + cipher = new CFBBlockCipher(engine, getIVLength() * 8); + break; + default: + throw new InvalidAlgorithmParameterException(_name); } return cipher; @@ -123,7 +122,6 @@ protected void _encrypt(byte[] data, ByteArrayOutputStream stream) { protected void _decrypt(byte[] data, ByteArrayOutputStream stream) { int noBytesProcessed; byte[] buffer = new byte[data.length]; - noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0); stream.write(buffer, 0, noBytesProcessed); } diff --git a/src/main/java/com/stfl/ss/CryptBase.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CryptBase.java similarity index 78% rename from src/main/java/com/stfl/ss/CryptBase.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CryptBase.java index 0c28b6a..ef98844 100644 --- a/src/main/java/com/stfl/ss/CryptBase.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CryptBase.java @@ -29,12 +29,15 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; -import com.stfl.misc.Util; + +import cc.springcloud.socks.Util; import org.bouncycastle.crypto.StreamBlockCipher; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.crypto.SecretKey; import java.io.ByteArrayOutputStream; @@ -42,7 +45,7 @@ import java.security.InvalidAlgorithmParameterException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Logger; + /** * Crypt base class implementation @@ -50,24 +53,28 @@ public abstract class CryptBase implements ICrypt { protected abstract StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException; + protected abstract SecretKey getKey(); + protected abstract void _encrypt(byte[] data, ByteArrayOutputStream stream); + protected abstract void _decrypt(byte[] data, ByteArrayOutputStream stream); - protected final String _name; protected final SecretKey _key; + protected final String _name; protected final ShadowSocksKey _ssKey; protected final int _ivLength; protected final int _keyLength; + protected boolean _encryptIVSet; protected boolean _decryptIVSet; - protected byte[] _encryptIV; - protected byte[] _decryptIV; + protected final byte[] _encryptIV; + protected final byte[] _decryptIV; protected final Lock encLock = new ReentrantLock(); protected final Lock decLock = new ReentrantLock(); protected StreamBlockCipher encCipher; protected StreamBlockCipher decCipher; - private Logger logger = Logger.getLogger(CryptBase.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); public CryptBase(String name, String password) { _name = name.toLowerCase(); @@ -75,38 +82,33 @@ public CryptBase(String name, String password) { _keyLength = getKeyLength(); _ssKey = new ShadowSocksKey(password, _keyLength); _key = getKey(); + _encryptIV = new byte[_ivLength]; + _decryptIV = new byte[_ivLength]; } - protected void setIV(byte[] iv, boolean isEncrypt) - { + + protected void setIV(byte[] iv, boolean isEncrypt) { if (_ivLength == 0) { return; } - - if (isEncrypt) - { - _encryptIV = new byte[_ivLength]; - System.arraycopy(iv, 0, _encryptIV, 0, _ivLength); - try { - encCipher = getCipher(isEncrypt); - ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _encryptIV); - encCipher.init(isEncrypt, parameterIV); - } catch (InvalidAlgorithmParameterException e) { - logger.info(e.toString()); - } + if (isEncrypt) { + encCipher = initCipher(iv, _encryptIV, true); + } else { + decCipher = initCipher(iv, _decryptIV, false); } - else - { - _decryptIV = new byte[_ivLength]; - System.arraycopy(iv, 0, _decryptIV, 0, _ivLength); - try { - decCipher = getCipher(isEncrypt); - ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _decryptIV); - decCipher.init(isEncrypt, parameterIV); - } catch (InvalidAlgorithmParameterException e) { - logger.info(e.toString()); - } + } + + private StreamBlockCipher initCipher(byte[] iv, byte[] _encryptIV, boolean isEncrypt) { + System.arraycopy(iv, 0, _encryptIV, 0, _ivLength); + StreamBlockCipher cipher = null; + try { + cipher = getCipher(isEncrypt); + ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(_key.getEncoded()), _encryptIV); + cipher.init(isEncrypt, parameterIV); + } catch (InvalidAlgorithmParameterException e) { + logger.info(e.toString()); } + return cipher; } @Override @@ -122,9 +124,7 @@ public void encrypt(byte[] data, ByteArrayOutputStream stream) { } catch (IOException e) { logger.info(e.toString()); } - } - _encrypt(data, stream); } } @@ -150,7 +150,6 @@ public void decrypt(byte[] data, ByteArrayOutputStream stream) { } else { temp = data; } - _decrypt(temp, stream); } } diff --git a/src/main/java/com/stfl/ss/CryptFactory.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CryptBuilder.java similarity index 72% rename from src/main/java/com/stfl/ss/CryptFactory.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CryptBuilder.java index 1b3d18c..f8cffd6 100644 --- a/src/main/java/com/stfl/ss/CryptFactory.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/CryptBuilder.java @@ -29,43 +29,37 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; -import com.stfl.misc.Reflection; import java.util.*; -import java.util.logging.Logger; /** * Crypt factory */ -public class CryptFactory { - private static final Map crypts = new HashMap() {{ +public class CryptBuilder { + + private static final Map crypts = new HashMap() {{ putAll(AesCrypt.getCiphers()); putAll(CamelliaCrypt.getCiphers()); putAll(BlowFishCrypt.getCiphers()); putAll(SeedCrypt.getCiphers()); // TODO: other crypts }}; - private static Logger logger = Logger.getLogger(CryptFactory.class.getName()); - public static boolean isCipherExisted(String name) { - return (crypts.get(name) != null); + public static boolean isCipherNotExisted(String name) { + return crypts.get(name) == null; } - public static ICrypt get(String name, String password) { - try { - Object obj = Reflection.get(crypts.get(name), String.class, name, String.class, password); - return (ICrypt)obj; - - } catch (Exception e) { - logger.info(com.stfl.misc.Util.getErrorMessage(e)); + public static ICrypt build(String name, String password) { + ICryptFactory crypt = crypts.get(name); + if (crypt != null) { + return crypt.getCrypt(name, password); } - return null; } public static List getSupportedCiphers() { - List sortedKeys = new ArrayList<>(crypts.keySet()); + List sortedKeys = new ArrayList<>(crypts.keySet()); Collections.sort(sortedKeys); return sortedKeys; } diff --git a/src/main/java/com/stfl/ss/ICrypt.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ICrypt.java similarity index 97% rename from src/main/java/com/stfl/ss/ICrypt.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ICrypt.java index 56b4ecf..b70b118 100644 --- a/src/main/java/com/stfl/ss/ICrypt.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ICrypt.java @@ -29,10 +29,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; import java.io.ByteArrayOutputStream; -import java.util.Map; /** * Interface of crypt diff --git a/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ICryptFactory.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ICryptFactory.java new file mode 100644 index 0000000..77a586f --- /dev/null +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ICryptFactory.java @@ -0,0 +1,9 @@ +package cc.springcloud.socks.ss; + +/** + * Created by XYUU on 2019/1/25. + */ +@FunctionalInterface +public interface ICryptFactory { + ICrypt getCrypt(String name,String password); +} diff --git a/src/main/java/com/stfl/ss/SeedCrypt.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/SeedCrypt.java similarity index 94% rename from src/main/java/com/stfl/ss/SeedCrypt.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/SeedCrypt.java index b150d59..5a1887f 100644 --- a/src/main/java/com/stfl/ss/SeedCrypt.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/SeedCrypt.java @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; import org.bouncycastle.crypto.StreamBlockCipher; import org.bouncycastle.crypto.engines.SEEDEngine; @@ -49,10 +49,9 @@ public class SeedCrypt extends CryptBase { public final static String CIPHER_SEED_CFB = "seed-cfb"; - public static Map getCiphers() { - Map ciphers = new HashMap<>(); - ciphers.put(CIPHER_SEED_CFB, SeedCrypt.class.getName()); - + public static Map getCiphers() { + Map ciphers = new HashMap<>(); + ciphers.put(CIPHER_SEED_CFB, SeedCrypt::new); return ciphers; } diff --git a/src/main/java/com/stfl/ss/ShadowSocksKey.java b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ShadowSocksKey.java similarity index 93% rename from src/main/java/com/stfl/ss/ShadowSocksKey.java rename to shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ShadowSocksKey.java index 08d4bb1..ba5e004 100644 --- a/src/main/java/com/stfl/ss/ShadowSocksKey.java +++ b/shadowsocks-core/src/main/java/cc/springcloud/socks/ss/ShadowSocksKey.java @@ -29,21 +29,23 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.ss; +package cc.springcloud.socks.ss; -import com.stfl.misc.Util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.crypto.SecretKey; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; -import java.util.logging.Logger; + /** * Shadowsocks key generator */ public class ShadowSocksKey implements SecretKey { - private Logger logger = Logger.getLogger(ShadowSocksKey.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); private final static int KEY_LENGTH = 32; private byte[] _key; private int _length; @@ -75,7 +77,7 @@ private byte[] init(String password) { logger.info("ShadowSocksKey: Unsupported string encoding"); } catch (Exception e) { - logger.info(Util.getErrorMessage(e)); + logger.info("init key error",e); return null; } @@ -103,7 +105,7 @@ private byte[] init(String password) { @Override public String getAlgorithm() { - return "shadowsocks"; + return "shadowSocks"; } @Override diff --git a/shadowsocks-ui/pom.xml b/shadowsocks-ui/pom.xml new file mode 100644 index 0000000..c9fed0d --- /dev/null +++ b/shadowsocks-ui/pom.xml @@ -0,0 +1,31 @@ + + + + shadowsocks-java + cc.springcloud + 0.1-SNAPSHOT + ../ + + 4.0.0 + shadowsocks-ui + 0.1-SNAPSHOT + + + cc.springcloud + shadowsocks-core + 0.1-SNAPSHOT + + + org.json + json + LATEST + + + org.slf4j + slf4j-jdk14 + 1.7.25 + + + \ No newline at end of file diff --git a/shadowsocks-ui/src/main/java/cc/springcloud/socks/Main.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/Main.java new file mode 100644 index 0000000..3fa6c57 --- /dev/null +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/Main.java @@ -0,0 +1,133 @@ +package cc.springcloud.socks; + +import cc.springcloud.socks.misc.JsonConfig; +import cc.springcloud.socks.network.NioLocalServer; +import cc.springcloud.socks.network.proxy.IProxy; +import cc.springcloud.socks.network.proxy.ProxyFactory; +import cc.springcloud.socks.ss.CryptBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + + +public class Main { + private static Logger logger = LoggerFactory.getLogger(Main.class); + + public static void main(String[] args) { + if (args.length != 0) { + startCommandLine(args); + } + else { + MainGui.launch(MainGui.class); + } + } + + private static void startCommandLine(String[] args) { + JsonConfig jsonConfig; + + jsonConfig = parseArgument(args); + if (jsonConfig == null) { + printUsage(); + return; + } + + jsonConfig.saveToJson(); + + try { + //LocalServer server = new LocalServer(jsonConfig); + NioLocalServer server = new NioLocalServer(jsonConfig); + Thread t = new Thread(server); + t.start(); + t.join(); + } catch (Exception e) { + logger.warn("Unable to start server: {}" , e); + } + } + + private static JsonConfig parseArgument(String[] args) { + JsonConfig jsonConfig = new JsonConfig(); + + if (args.length == 2) { + if (args[0].equals("--jsonConfig")) { + Path path = Paths.get(args[1]); + try { + String json = new String(Files.readAllBytes(path)); + jsonConfig.loadFromJson(json); + } catch (IOException e) { + System.out.println("Unable to read configuration file: " + args[1]); + return null; + } + return jsonConfig; + } + else { + return null; + } + } + + if (args.length != 8) { + return null; + } + + // parse arguments + for (int i = 0; i < args.length; i+=2) { + String[] tempArgs; + switch (args[i]) { + case "--local": + tempArgs = args[i + 1].split(":"); + if (tempArgs.length < 2) { + System.out.println("Invalid argument: " + args[i]); + return null; + } + jsonConfig.setLocalIpAddress(tempArgs[0]); + jsonConfig.setLocalPort(Integer.parseInt(tempArgs[1])); + break; + case "--remote": + tempArgs = args[i + 1].split(":"); + if (tempArgs.length < 2) { + System.out.println("Invalid argument: " + args[i]); + return null; + } + jsonConfig.setRemoteIpAddress(tempArgs[0]); + jsonConfig.setRemotePort(Integer.parseInt(tempArgs[1])); + break; + case "--cipher": + jsonConfig.setMethod(args[i + 1]); + break; + case "--password": + jsonConfig.setPassword(args[i + 1]); + break; + case "--proxy": + jsonConfig.setProxyType(args[i + 1]); + break; + } + } + + return jsonConfig; + } + + private static void printUsage() { + System.out.println("Usage: ss --[option] value --[option] value..."); + System.out.println("Option:"); + System.out.println(" --local [IP:PORT]"); + System.out.println(" --remote [IP:PORT]"); + System.out.println(" --cipher [CIPHER_NAME]"); + System.out.println(" --password [PASSWORD]"); + System.out.println(" --config [CONFIG_FILE]"); + System.out.println(" --proxy [TYPE]"); + System.out.println("Support Proxy Type:"); + for (IProxy.TYPE t : ProxyFactory.getSupportedProxyTypes()) { + System.out.printf(" %s\n", t.toString().toLowerCase()); + } + System.out.println("Support Ciphers:"); + for (String s : CryptBuilder.getSupportedCiphers()) { + System.out.printf(" %s\n", s); + } + System.out.println("Example:"); + System.out.println(" ss --local \"127.0.0.1:1080\" --remote \"[SS_SERVER_IP]:1080\" --cipher \"aes-256-cfb\" --password \"HelloWorld\""); + System.out.println(" ss --config config.json"); + } +} diff --git a/src/main/java/com/stfl/MainGui.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/MainGui.java similarity index 62% rename from src/main/java/com/stfl/MainGui.java rename to shadowsocks-ui/src/main/java/cc/springcloud/socks/MainGui.java index dbcf40d..9c0ca34 100644 --- a/src/main/java/com/stfl/MainGui.java +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/MainGui.java @@ -1,7 +1,7 @@ -package com.stfl; +package cc.springcloud.socks; -import com.stfl.misc.UTF8Control; -import com.stfl.ui.MainLayoutController; +import cc.springcloud.socks.misc.UTF8Control; +import cc.springcloud.socks.ui.MainLayoutController; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; @@ -9,24 +9,26 @@ import javafx.scene.image.Image; import javafx.scene.layout.Pane; import javafx.stage.Stage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.io.IOException; +import java.util.Locale; import java.util.ResourceBundle; -import java.util.logging.Logger; + public class MainGui extends Application { - private static Logger logger = Logger.getLogger(MainGui.class.getName()); + + private Logger logger = LoggerFactory.getLogger(getClass()); private Stage primaryStage; private Scene rootScene; private MainLayoutController controller; private TrayIcon trayIcon; @Override - public void start(Stage primaryStage) throws Exception { + public void start(Stage primaryStage) { Platform.setImplicitExit(false); this.primaryStage = primaryStage; @@ -35,7 +37,7 @@ public void start(Stage primaryStage) throws Exception { try { // Load the root layout from the fxml file FXMLLoader mainLayoutLoader = new FXMLLoader(MainGui.class.getResource("/resources/ui/MainLayout.fxml")); - mainLayoutLoader.setResources(ResourceBundle.getBundle("resources.bundle.ui", Constant.LOCALE, new UTF8Control())); + mainLayoutLoader.setResources(ResourceBundle.getBundle("resources.bundle.ui", Locale.getDefault(), new UTF8Control())); Pane rootLayout = mainLayoutLoader.load(); rootScene = new Scene(rootLayout); @@ -51,7 +53,7 @@ public void start(Stage primaryStage) throws Exception { primaryStage.show(); } catch (IOException e) { // Exception gets thrown if the fxml file could not be loaded - e.printStackTrace(); + logger.info("start error",e); } } @@ -62,7 +64,7 @@ private void addToTray() { // make sure system tray is supported if (!java.awt.SystemTray.isSupported()) { - logger.warning("No system tray support!"); + logger.warn("No system tray support!"); } final java.awt.SystemTray tray = java.awt.SystemTray.getSystemTray(); @@ -70,39 +72,16 @@ private void addToTray() { java.awt.Image image = ImageIO.read(MainGui.class.getResource("/resources/image/icon.png")); trayIcon = new TrayIcon(image); - trayIcon.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Platform.runLater(new Runnable() { - @Override - public void run() { - primaryStage.show(); - } - }); - } - }); + trayIcon.addActionListener(e -> Platform.runLater(() -> primaryStage.show())); java.awt.MenuItem openItem = new java.awt.MenuItem("Configuration"); - openItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Platform.runLater(new Runnable() { - @Override - public void run() { - show(); - } - }); - } - }); + openItem.addActionListener(e -> Platform.runLater(() -> show())); java.awt.MenuItem exitItem = new java.awt.MenuItem("Exit"); - exitItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - controller.closeServer(); - Platform.exit(); - tray.remove(trayIcon); - } + exitItem.addActionListener(e -> { + controller.closeServer(); + Platform.exit(); + tray.remove(trayIcon); }); PopupMenu popup = new PopupMenu(); @@ -112,10 +91,8 @@ public void actionPerformed(ActionEvent e) { trayIcon.setPopupMenu(popup); trayIcon.setToolTip("Not Connected"); tray.add(trayIcon); - } catch (IOException e) { - e.printStackTrace(); - } catch (AWTException e) { - e.printStackTrace(); + } catch (IOException | AWTException e) { + logger.info("addToTray error",e); } } diff --git a/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/JsonConfig.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/JsonConfig.java new file mode 100644 index 0000000..ab59fc9 --- /dev/null +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/JsonConfig.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2015, Blake + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package cc.springcloud.socks.misc; + +import cc.springcloud.socks.network.Config; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Data class for configuration to bring up server + */ +public class JsonConfig extends Config { + + private static final String CONF_FILE = "config.json"; + private Logger logger = LoggerFactory.getLogger(getClass()); + + public void loadFromJson() { + Path path = Paths.get(CONF_FILE); + try { + loadFromJson(new String(Files.readAllBytes(path))); + } catch (IOException e) { + logger.info("load json config file error", e); + } + } + + public void loadFromJson(String jsonStr) { + if (jsonStr != null) { + JSONObject json = new JSONObject(jsonStr); + this.setRemoteIpAddress(json.getString("remoteIpAddress")). + setRemotePort(json.getInt("remotePort")). + setLocalIpAddress(json.getString("localIpAddress")). + setLocalPort(json.getInt("localPort")). + setMethod(json.getString("method")). + setPassword(json.getString("password")). + setLogLevel(json.getString("logLevel")). + setProxyType(json.getString("proxyType")); + } + } + + @Override + public Config setLogLevel(String logLevel) { + super.setLogLevel(logLevel); + Log.init(logLevel); + return this; + } + + public void saveToJson() { + JSONObject json = new JSONObject(); + json.put("remoteIpAddress", getRemoteIpAddress()); + json.put("remotePort", getRemotePort()); + json.put("localIpAddress", getLocalIpAddress()); + json.put("localPort", getLocalPort()); + json.put("method", getMethod()); + json.put("password", getPassword()); + json.put("proxyType", getProxyType()); + json.put("logLevel", getLogLevel()); + try { + PrintWriter writer = new PrintWriter(CONF_FILE) { + private final static String indent = " "; + private final String LINE_SEP = System.getProperty("line.separator"); + private int indentLevel = 0; + + @Override + public void write(int c) { + char ch = (char) c; + if (ch == '[' || ch == '{') { + super.write(c); + super.write(LINE_SEP); + indentLevel++; + writeIndentation(); + } else if (ch == ']' || ch == '}') { + super.write(LINE_SEP); + indentLevel--; + writeIndentation(); + super.write(c); + } else if (ch == ':') { + super.write(c); + super.write(" "); + } else if (ch == ',') { + super.write(c); + super.write(LINE_SEP); + writeIndentation(); + } else { + super.write(c); + } + } + + private void writeIndentation() { + for (int i = 0; i < indentLevel; i++) { + super.write(indent); + } + } + }; + json.write(writer); + writer.close(); + } catch (FileNotFoundException e) { + logger.info("save json config file error", e); + } + } + +} diff --git a/src/main/java/com/stfl/misc/Log.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/Log.java similarity index 93% rename from src/main/java/com/stfl/misc/Log.java rename to shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/Log.java index e8ec81e..86ec021 100644 --- a/src/main/java/com/stfl/misc/Log.java +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/Log.java @@ -29,16 +29,21 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.stfl.misc; +package cc.springcloud.socks.misc; + import java.util.Locale; import java.util.Properties; -import java.util.logging.*; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Initialized level of root logger */ public class Log { + private static boolean handlerInit = false; public static void init() { @@ -85,12 +90,11 @@ public static void addHandler(Handler handler) { for (Handler h : rootLogger.getHandlers()) { logLevel = h.getLevel(); } - handler.setLevel(logLevel); rootLogger.addHandler(handler); } private static Logger getRootLogger() { - return Logger.getLogger("com.stfl"); + return Logger.getLogger("cc.springcloud.socks"); } } diff --git a/src/main/java/com/stfl/misc/UTF8Control.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/UTF8Control.java similarity index 93% rename from src/main/java/com/stfl/misc/UTF8Control.java rename to shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/UTF8Control.java index 18b16bc..02ea4bb 100644 --- a/src/main/java/com/stfl/misc/UTF8Control.java +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/misc/UTF8Control.java @@ -1,4 +1,4 @@ -package com.stfl.misc; +package cc.springcloud.socks.misc; import java.io.IOException; import java.io.InputStream; @@ -12,7 +12,7 @@ public class UTF8Control extends ResourceBundle.Control { public ResourceBundle newBundle (String baseName, Locale locale, String format, ClassLoader loader, boolean reload) - throws IllegalAccessException, InstantiationException, IOException + throws IOException { // below is the original implementation String bundleName = toBundleName(baseName, locale); diff --git a/src/main/java/com/stfl/ui/LogLayoutController.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/LogLayoutController.java similarity index 90% rename from src/main/java/com/stfl/ui/LogLayoutController.java rename to shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/LogLayoutController.java index df74d91..bc279b0 100644 --- a/src/main/java/com/stfl/ui/LogLayoutController.java +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/LogLayoutController.java @@ -1,6 +1,6 @@ -package com.stfl.ui; +package cc.springcloud.socks.ui; -import com.stfl.misc.Log; +import cc.springcloud.socks.misc.Log; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.TextArea; diff --git a/src/main/java/com/stfl/ui/MainLayoutController.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/MainLayoutController.java similarity index 53% rename from src/main/java/com/stfl/ui/MainLayoutController.java rename to shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/MainLayoutController.java index 21aa267..9a54eec 100644 --- a/src/main/java/com/stfl/ui/MainLayoutController.java +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/MainLayoutController.java @@ -1,29 +1,33 @@ -package com.stfl.ui; - -import com.stfl.Constant; -import com.stfl.MainGui; -import com.stfl.misc.Config; -import com.stfl.misc.UTF8Control; -import com.stfl.misc.Util; -import com.stfl.network.IServer; -import com.stfl.network.NioLocalServer; -import com.stfl.network.proxy.IProxy; -import com.stfl.network.proxy.ProxyFactory; -import com.stfl.ss.CryptFactory; +package cc.springcloud.socks.ui; + +import cc.springcloud.socks.Constant; +import cc.springcloud.socks.MainGui; +import cc.springcloud.socks.misc.JsonConfig; +import cc.springcloud.socks.misc.UTF8Control; +import cc.springcloud.socks.network.IServer; +import cc.springcloud.socks.network.NioLocalServer; +import cc.springcloud.socks.network.proxy.IProxy; +import cc.springcloud.socks.network.proxy.ProxyFactory; +import cc.springcloud.socks.ss.CryptBuilder; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; -import javafx.scene.control.*; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.layout.Pane; import javafx.stage.Stage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; +import java.util.Locale; import java.util.ResourceBundle; -import java.util.logging.Logger; public class MainLayoutController { @@ -32,13 +36,13 @@ public class MainLayoutController { @FXML private TextField txtServerPort; @FXML - private ComboBox cboCipher; + private ComboBox cboCipher; @FXML private TextField txtPassword; @FXML private TextField txtLocalPort; @FXML - private ComboBox cboProxyType; + private ComboBox cboProxyType; @FXML private Button btnStart; @FXML @@ -48,17 +52,17 @@ public class MainLayoutController { @FXML private Button btnClose; - private Logger logger = Logger.getLogger(MainLayoutController.class.getName()); + private Logger logger = LoggerFactory.getLogger(getClass()); private MainGui gui; private IServer server; private Stage logStage; - private Config config; + private JsonConfig jsonConfig; @FXML private void initialize() { // set cipher options ObservableList ciphers = FXCollections.observableArrayList(); - ciphers.addAll(CryptFactory.getSupportedCiphers()); + ciphers.addAll(CryptBuilder.getSupportedCiphers()); cboCipher.setItems(ciphers); // set proxy options @@ -67,20 +71,20 @@ private void initialize() { cboProxyType.setItems(proxyTypes); // prepare configuration - config = new Config(); - config.loadFromJson(Util.getFileContent(Constant.CONF_FILE)); - txtServerIP.setText(config.getRemoteIpAddress()); - txtServerPort.setText(String.valueOf(config.getRemotePort())); - txtLocalPort.setText(String.valueOf(config.getLocalPort())); - txtPassword.setText(config.getPassword()); - cboCipher.setValue(config.getMethod()); - cboProxyType.setValue(config.getProxyType()); + jsonConfig = new JsonConfig(); + jsonConfig.loadFromJson(); + txtServerIP.setText(jsonConfig.getRemoteIpAddress()); + txtServerPort.setText(String.valueOf(jsonConfig.getRemotePort())); + txtLocalPort.setText(String.valueOf(jsonConfig.getLocalPort())); + txtPassword.setText(jsonConfig.getPassword()); + cboCipher.setValue(jsonConfig.getMethod()); + cboProxyType.setValue(IProxy.TYPE.valueOf(jsonConfig.getProxyType())); // prepare log window Stage stage = new Stage(); try { FXMLLoader logLayoutLoader = new FXMLLoader(MainGui.class.getResource("/resources/ui/LogLayout.fxml")); - logLayoutLoader.setResources(ResourceBundle.getBundle("resources.bundle.ui", Constant.LOCALE, new UTF8Control())); + logLayoutLoader.setResources(ResourceBundle.getBundle("resources.bundle.ui", Locale.getDefault(), new UTF8Control())); Pane logLayout = logLayoutLoader.load(); Scene logScene = new Scene(logLayout); stage.setTitle("Log"); @@ -92,7 +96,7 @@ private void initialize() { controller.setStage(stage); logStage = stage; } catch (IOException e) { - logger.warning("Unable to load ICON: " + e.toString()); + logger.warn("Unable to load ICON: {}", e); } btnStop.setDisable(true); @@ -100,59 +104,48 @@ private void initialize() { @FXML private void handleStart() { - boolean isValidated = false; - do { - if (!txtServerIP.getText().matches("[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}")) { - showAlert(Constant.PROG_NAME, "Invalid IP address", Alert.AlertType.ERROR); - break; - } - String ip = txtServerIP.getText(); - if (!txtServerPort.getText().matches("[0-9]+")) { - showAlert(Constant.PROG_NAME, "Invalid Port", Alert.AlertType.ERROR); - break; - } - int port = Integer.parseInt(txtServerPort.getText()); - - String method = (String) cboCipher.getValue(); - if (txtPassword.getText().length() == 0) { - showAlert(Constant.PROG_NAME, "Please specified password", Alert.AlertType.ERROR); - break; - } - String password = txtPassword.getText(); - IProxy.TYPE type = (IProxy.TYPE) cboProxyType.getValue(); - if (!txtLocalPort.getText().matches("[0-9]+")) { - showAlert(Constant.PROG_NAME, "Invalid Port", Alert.AlertType.ERROR); - break; - } - int localPort = Integer.parseInt(txtLocalPort.getText()); - - // create config - config.setRemoteIpAddress(ip); - config.setRemotePort(port); - config.setLocalIpAddress("127.0.0.1"); - config.setLocalPort(localPort); - config.setMethod(method); - config.setPassword(password); - config.setProxyType(type); - Util.saveFile(Constant.CONF_FILE, config.saveToJson()); - - isValidated = true; - } while (false); - - if (!isValidated) + if (!txtServerIP.getText().matches("[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}.[0-9]{1,4}")) { + showAlert(Constant.PROGRAM_NAME, "Invalid IP address", Alert.AlertType.ERROR); return; - + } + String ip = txtServerIP.getText(); + if (!txtServerPort.getText().matches("[0-9]+")) { + showAlert(Constant.PROGRAM_NAME, "Invalid Port", Alert.AlertType.ERROR); + return; + } + int port = Integer.parseInt(txtServerPort.getText()); + String method = cboCipher.getValue(); + if (txtPassword.getText().length() == 0) { + showAlert(Constant.PROGRAM_NAME, "Please specified password", Alert.AlertType.ERROR); + return; + } + String password = txtPassword.getText(); + IProxy.TYPE type = cboProxyType.getValue(); + if (!txtLocalPort.getText().matches("[0-9]+")) { + showAlert(Constant.PROGRAM_NAME, "Invalid Port", Alert.AlertType.ERROR); + return; + } + int localPort = Integer.parseInt(txtLocalPort.getText()); + // create jsonConfig + jsonConfig.setRemoteIpAddress(ip); + jsonConfig.setRemotePort(port); + jsonConfig.setLocalIpAddress("127.0.0.1"); + jsonConfig.setLocalPort(localPort); + jsonConfig.setMethod(method); + jsonConfig.setPassword(password); + jsonConfig.setProxyType(type.name()); + jsonConfig.saveToJson(); // start start try { - server = new NioLocalServer(config); + server = new NioLocalServer(jsonConfig); Thread t = new Thread(server); t.setDaemon(true); t.start(); - String message = String.format("(Connected) Server %s:%d", config.getRemoteIpAddress(), config.getRemotePort()); + String message = String.format("(Connected) Server %s:%d", jsonConfig.getRemoteIpAddress(), jsonConfig.getRemotePort()); gui.setTooltip(message); gui.showNotification(message); } catch (IOException | InvalidAlgorithmParameterException e) { - logger.warning("Unable to start server: " + e.toString()); + logger.warn("Unable to start server: {}", e); } btnStop.setDisable(false); btnStart.setDisable(true); @@ -162,7 +155,7 @@ private void handleStart() { private void handleStop() { if (server != null) { server.close(); - String message = String.format("(Disconnected) Server %s:%d", config.getRemoteIpAddress(), config.getRemotePort()); + String message = String.format("(Disconnected) Server %s:%d", jsonConfig.getRemoteIpAddress(), jsonConfig.getRemotePort()); gui.showNotification(message); gui.setTooltip("Not Connected"); } diff --git a/src/main/java/com/stfl/ui/TextAreaLogHandler.java b/shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/TextAreaLogHandler.java similarity index 56% rename from src/main/java/com/stfl/ui/TextAreaLogHandler.java rename to shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/TextAreaLogHandler.java index 05b0d32..568ddb1 100644 --- a/src/main/java/com/stfl/ui/TextAreaLogHandler.java +++ b/shadowsocks-ui/src/main/java/cc/springcloud/socks/ui/TextAreaLogHandler.java @@ -1,4 +1,4 @@ -package com.stfl.ui; +package cc.springcloud.socks.ui; import javafx.application.Platform; import javafx.scene.control.TextArea; @@ -7,7 +7,7 @@ import java.util.logging.StreamHandler; public class TextAreaLogHandler extends StreamHandler { - TextArea textArea = null; + private TextArea textArea; public void setTextArea(TextArea textArea) { this.textArea = textArea; @@ -20,15 +20,12 @@ public void publish(LogRecord record) { flush(); if (textArea != null) { - Platform.runLater(new Runnable() { - @Override - public void run() { - // limited log size to 64k - if (textArea.getText().length() > 65535) { - textArea.clear(); - } - textArea.appendText(getFormatter().format(lg)); + Platform.runLater(() -> { + // limited log size to 64k + if (textArea.getText().length() > 65535) { + textArea.clear(); } + textArea.appendText(getFormatter().format(lg)); }); } } diff --git a/src/main/resources/META-INF/MANIFEST.MF b/shadowsocks-ui/src/main/resources/META-INF/MANIFEST.MF similarity index 57% rename from src/main/resources/META-INF/MANIFEST.MF rename to shadowsocks-ui/src/main/resources/META-INF/MANIFEST.MF index 5a00b74..614d570 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/shadowsocks-ui/src/main/resources/META-INF/MANIFEST.MF @@ -1,4 +1,4 @@ Manifest-Version: 1.0 Class-Path: bcprov-jdk15on-1.52.jar json-simple-1.1.1.jar -Main-Class: com.stfl.Main +cc.springcloud.socks.Main-Class: cc.springcloud.socks.Main diff --git a/src/main/resources/resources/bundle/ui_en.properties b/shadowsocks-ui/src/main/resources/resources/bundle/ui_en.properties similarity index 100% rename from src/main/resources/resources/bundle/ui_en.properties rename to shadowsocks-ui/src/main/resources/resources/bundle/ui_en.properties diff --git a/src/main/resources/resources/bundle/ui_zh_CN.properties b/shadowsocks-ui/src/main/resources/resources/bundle/ui_zh_CN.properties similarity index 100% rename from src/main/resources/resources/bundle/ui_zh_CN.properties rename to shadowsocks-ui/src/main/resources/resources/bundle/ui_zh_CN.properties diff --git a/src/main/resources/resources/bundle/ui_zh_TW.properties b/shadowsocks-ui/src/main/resources/resources/bundle/ui_zh_TW.properties similarity index 100% rename from src/main/resources/resources/bundle/ui_zh_TW.properties rename to shadowsocks-ui/src/main/resources/resources/bundle/ui_zh_TW.properties diff --git a/src/main/resources/resources/image/icon.png b/shadowsocks-ui/src/main/resources/resources/image/icon.png similarity index 100% rename from src/main/resources/resources/image/icon.png rename to shadowsocks-ui/src/main/resources/resources/image/icon.png diff --git a/src/main/resources/resources/ui/LogLayout.fxml b/shadowsocks-ui/src/main/resources/resources/ui/LogLayout.fxml similarity index 57% rename from src/main/resources/resources/ui/LogLayout.fxml rename to shadowsocks-ui/src/main/resources/resources/ui/LogLayout.fxml index d55c74e..d6120e3 100644 --- a/src/main/resources/resources/ui/LogLayout.fxml +++ b/shadowsocks-ui/src/main/resources/resources/ui/LogLayout.fxml @@ -1,17 +1,22 @@ - - - + + + + + - +