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 @@
-
-
-
+
+
+
+
+
-
+
-
+
+
+
+
diff --git a/src/main/resources/resources/ui/MainLayout.fxml b/shadowsocks-ui/src/main/resources/resources/ui/MainLayout.fxml
similarity index 78%
rename from src/main/resources/resources/ui/MainLayout.fxml
rename to shadowsocks-ui/src/main/resources/resources/ui/MainLayout.fxml
index 5e8fc17..39e8bed 100644
--- a/src/main/resources/resources/ui/MainLayout.fxml
+++ b/shadowsocks-ui/src/main/resources/resources/ui/MainLayout.fxml
@@ -1,10 +1,13 @@
-
-
-
+
+
+
+
+
+
-
+
@@ -18,7 +21,7 @@
-
+
diff --git a/src/main/java/com/stfl/Constant.java b/src/main/java/com/stfl/Constant.java
deleted file mode 100644
index d6c163d..0000000
--- a/src/main/java/com/stfl/Constant.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.stfl;
-
-import java.util.Locale;
-
-public class Constant {
- public static final String PROG_NAME = "shadowsocks-java";
- public static final String VERSION = "0.2";
- public static final int BUFFER_SIZE = 16384;
- public static final String CONF_FILE = "config.json";
- public static final Locale LOCALE = Locale.getDefault();
-}
diff --git a/src/main/java/com/stfl/Main.java b/src/main/java/com/stfl/Main.java
deleted file mode 100644
index e70e8cc..0000000
--- a/src/main/java/com/stfl/Main.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package com.stfl;
-
-import com.stfl.misc.Config;
-import com.stfl.misc.Util;
-import com.stfl.network.LocalServer;
-import com.stfl.network.NioLocalServer;
-import com.stfl.network.proxy.IProxy;
-import com.stfl.network.proxy.ProxyFactory;
-import com.stfl.ss.CryptFactory;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.logging.Logger;
-
-public class Main {
- private static Logger logger = Logger.getLogger(Main.class.getName());
-
- public static void main(String[] args) {
- if (args.length != 0) {
- startCommandLine(args);
- }
- else {
- MainGui.launch(MainGui.class);
- }
- }
-
- private static void startCommandLine(String[] args) {
- Config config;
-
- config = parseArgument(args);
- if (config == null) {
- printUsage();
- return;
- }
-
- Util.saveFile(Constant.CONF_FILE, config.saveToJson());
-
- try {
- //LocalServer server = new LocalServer(config);
- NioLocalServer server = new NioLocalServer(config);
- Thread t = new Thread(server);
- t.start();
- t.join();
- } catch (Exception e) {
- logger.warning("Unable to start server: " + e.toString());
- }
- }
-
- private static Config parseArgument(String[] args) {
- Config config = new Config();
-
- if (args.length == 2) {
- if (args[0].equals("--config")) {
- Path path = Paths.get(args[1]);
- try {
- String json = new String(Files.readAllBytes(path));
- config.loadFromJson(json);
- } catch (IOException e) {
- System.out.println("Unable to read configuration file: " + args[1]);
- return null;
- }
- return config;
- }
- else {
- return null;
- }
- }
-
- if (args.length != 8) {
- return null;
- }
-
- // parse arguments
- for (int i = 0; i < args.length; i+=2) {
- String[] tempArgs;
- if (args[i].equals("--local")) {
- tempArgs = args[i+1].split(":");
- if (tempArgs.length < 2) {
- System.out.println("Invalid argument: " + args[i]);
- return null;
- }
- config.setLocalIpAddress(tempArgs[0]);
- config.setLocalPort(Integer.parseInt(tempArgs[1]));
- }
- else if (args[i].equals("--remote")) {
- tempArgs = args[i+1].split(":");
- if (tempArgs.length < 2) {
- System.out.println("Invalid argument: " + args[i]);
- return null;
- }
- config.setRemoteIpAddress(tempArgs[0]);
- config.setRemotePort(Integer.parseInt(tempArgs[1]));
- }
- else if (args[i].equals("--cipher")) {
- config.setMethod(args[i+1]);
- }
- else if (args[i].equals("--password")) {
- config.setPassword(args[i + 1]);
- }
- else if (args[i].equals("--proxy")) {
- config.setProxyType(args[i + 1]);
- }
- }
-
- return config;
- }
-
- 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 : CryptFactory.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/misc/Config.java b/src/main/java/com/stfl/misc/Config.java
deleted file mode 100644
index 2654669..0000000
--- a/src/main/java/com/stfl/misc/Config.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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 com.stfl.misc;
-
-import com.stfl.network.proxy.IProxy;
-import org.json.simple.JSONObject;
-import org.json.simple.JSONValue;
-import com.stfl.ss.AesCrypt;
-
-/**
- * Data class for configuration to bring up server
- */
-public class Config {
- private String _ipAddr;
- private int _port;
- private String _localIpAddr;
- private int _localPort;
- private String _method;
- private String _password;
- private String _logLevel;
- private IProxy.TYPE _proxyType;
-
- public Config() {
- loadFromJson("");
- }
-
- public Config(String ipAddr, int port, String localIpAddr, int localPort, String method, String password) {
- this();
- _ipAddr = ipAddr;
- _port = port;
- _localIpAddr = localIpAddr;
- _localPort = localPort;
- _method = method;
- _password = password;
- _proxyType = IProxy.TYPE.AUTO;
- }
-
- public Config(String ipAddr, int port, String localIpAddr, int localPort, String method, String password, IProxy.TYPE type) {
- this(ipAddr, port, localIpAddr, localPort, method, password);
- _proxyType = type;
- }
-
- public void setRemoteIpAddress(String value) {
- _ipAddr = value;
- }
-
- public String getRemoteIpAddress() {
- return _ipAddr;
- }
-
- public void setLocalIpAddress(String value) {
- _localIpAddr = value;
- }
-
- public String getLocalIpAddress() {
- return _localIpAddr;
- }
-
- public void setRemotePort(int value) {
- _port = value;
- }
-
- public int getRemotePort() {
- return _port;
- }
-
- public void setLocalPort(int value) {
- _localPort = value;
- }
-
- public int getLocalPort() {
- return _localPort;
- }
-
- public void setProxyType(String value) {
- _proxyType = IProxy.TYPE.AUTO;
- if (value.toLowerCase().equals(IProxy.TYPE.HTTP.toString().toLowerCase())) {
- _proxyType = IProxy.TYPE.HTTP;
- }
- else if (value.toLowerCase().equals(IProxy.TYPE.SOCKS5.toString().toLowerCase())) {
- _proxyType = IProxy.TYPE.SOCKS5;
- }
- }
-
- public void setProxyType(IProxy.TYPE value) {
- _proxyType = value;
- }
- public IProxy.TYPE getProxyType() {
- return _proxyType;
- }
-
- public void setMethod(String value) {
- _method = value;
- }
-
- public String getMethod() {
- return _method;
- }
-
- public void setPassword(String value) {
- _password = value;
- }
-
- public String getPassword() {
- return _password;
- }
-
- public void setLogLevel(String value) {
- _logLevel = value;
- Log.init(getLogLevel());
- }
-
- public String getLogLevel() {
- return _logLevel;
- }
-
- public void loadFromJson(String jsonStr) {
- if (jsonStr.length() == 0) {
- jsonStr = "{}";
- }
-
- JSONObject jObj = (JSONObject)JSONValue.parse(jsonStr);
- _ipAddr = (String)jObj.getOrDefault("remoteIpAddress", "");
- _port = ((Number)jObj.getOrDefault("remotePort", 1080)).intValue();
- _localIpAddr = (String)jObj.getOrDefault("localIpAddress", "127.0.0.1");
- _localPort = ((Number)jObj.getOrDefault("localPort", 1080)).intValue();
- _method = (String)jObj.getOrDefault("method", AesCrypt.CIPHER_AES_256_CFB);
- _password = (String)jObj.getOrDefault("password", "");
- _logLevel = (String)jObj.getOrDefault("logLevel", "INFO");
- setProxyType((String) jObj.getOrDefault("proxyType", IProxy.TYPE.SOCKS5.toString().toLowerCase()));
- setLogLevel(_logLevel);
- }
-
- public String saveToJson() {
- JSONObject jObj = new JSONObject();
- jObj.put("remoteIpAddress", _ipAddr);
- jObj.put("remotePort", _port);
- jObj.put("localIpAddress", _localIpAddr);
- jObj.put("localPort", _localPort);
- jObj.put("method", _method);
- jObj.put("password", _password);
- jObj.put("proxyType", _proxyType.toString().toLowerCase());
- jObj.put("logLevel", _logLevel);
-
- return Util.prettyPrintJson(jObj);
- }
-}
diff --git a/src/main/java/com/stfl/misc/Reflection.java b/src/main/java/com/stfl/misc/Reflection.java
deleted file mode 100644
index c41495d..0000000
--- a/src/main/java/com/stfl/misc/Reflection.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.stfl.misc;
-
-import java.lang.reflect.Constructor;
-
-public class Reflection {
- public static Object get(String className, Object... args) {
- Object retValue = null;
- try {
- Class c = Class.forName(className);
- if (args.length == 0) {
- retValue = c.newInstance();
- }
- else if ((args.length & 1) == 0) {
- // args should come with pairs, for example
- // String.class, "arg1_value", String.class, "arg2_value"
- Class[] oParam = new Class[args.length / 2];
- for (int arg_i = 0, i = 0; arg_i < args.length; arg_i+=2, i++) {
- oParam[i] = (Class)args[arg_i];
- }
-
- Constructor constructor = c.getConstructor(oParam);
- Object[] paramObjs = new Object[args.length / 2];
- for (int arg_i = 1, i = 0; arg_i < args.length; arg_i+=2, i++) {
- paramObjs[i] = args[arg_i];
- }
- retValue = constructor.newInstance(paramObjs);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- return retValue;
- }
-}
diff --git a/src/main/java/com/stfl/misc/Util.java b/src/main/java/com/stfl/misc/Util.java
deleted file mode 100644
index 1ba72a0..0000000
--- a/src/main/java/com/stfl/misc/Util.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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 com.stfl.misc;
-
-import com.stfl.network.proxy.Socks5Proxy;
-import org.json.simple.JSONObject;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.SecureRandom;
-
-/**
- * Helper class
- */
-public class Util {
- public static String dumpBytes(byte[] a) {
- StringBuilder sb = new StringBuilder(a.length * 2);
- for(byte b: a)
- sb.append(String.format("%x", b & 0xff));
- return sb.toString();
- }
-
- public static byte[] randomBytes(int size) {
- byte[] bytes = new byte[size];
- new SecureRandom().nextBytes(bytes);
- return bytes;
- }
-
- public static String getErrorMessage(Throwable e) {
- Writer writer = new StringWriter();
- PrintWriter pWriter = new PrintWriter(writer);
- e.printStackTrace(pWriter);
- return writer.toString();
- }
-
- public static String prettyPrintJson(JSONObject jObj) {
- String retValue;
- StringWriter writer = new StringWriter() {
- 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);
- }
- }
- };
-
- try {
- jObj.writeJSONString(writer);
- retValue = writer.toString();
- } catch (IOException e) {
- // something wrong with writer, use the original method
- retValue = jObj.toJSONString();
- }
-
- return retValue;
- }
-
- 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 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;
- }
-
- 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 boolean saveFile(String fn, String content) {
- PrintWriter writer;
- try {
- writer = new PrintWriter(fn);
- writer.println(content);
- writer.close();
- } catch (FileNotFoundException e) {
- return false;
- }
- return true;
- }
-
- public static String getFileContent(String fn) {
- Path path = Paths.get(fn);
- String content = "";
- try {
- content = new String(Files.readAllBytes(path));
- } catch (IOException e) {
- // do nothing
- }
-
- return content;
- }
-
- private static short byteToUnsignedByte(byte b) {
- return (short)(b & 0xff);
- }
-
- private static int getPort(byte b, byte b1) {
- return byteToUnsignedByte(b) << 8 | byteToUnsignedByte(b1);
- }
-}
diff --git a/src/main/java/com/stfl/network/NioLocalServer.java b/src/main/java/com/stfl/network/NioLocalServer.java
deleted file mode 100644
index b87512f..0000000
--- a/src/main/java/com/stfl/network/NioLocalServer.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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 com.stfl.network;
-
-import com.stfl.Constant;
-import com.stfl.misc.Config;
-import com.stfl.misc.Util;
-import com.stfl.network.nio.*;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.nio.channels.spi.SelectorProvider;
-import java.security.InvalidAlgorithmParameterException;
-import java.util.*;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.logging.Logger;
-
-/**
- * Non-blocking local server for shadowsocks
- */
-public class NioLocalServer extends SocketHandlerBase {
- private Logger logger = Logger.getLogger(NioLocalServer.class.getName());
-
- 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(config.getProxyType() + " Proxy Server starts at port: " + config.getLocalPort());
- }
-
- @Override
- protected Selector initSelector() throws IOException {
- Selector socketSelector = SelectorProvider.provider().openSelector();
- _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 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.warning("NioLocalServer::processPendingRequest (drop): " + key + request.socket);
- }
- break;
- case ChangeRequest.CLOSE_CHANNEL:
- cleanUp(request.socket);
- break;
- }
- return true;
- }
-
- @Override
- protected void processSelect(SelectionKey key) {
- // Handle event
- try {
- if (key.isAcceptable()) {
- accept(key);
- } else if (key.isReadable()) {
- read(key);
- } else if (key.isWritable()) {
- write(key);
- }
- }
- catch (IOException e) {
- cleanUp((SocketChannel)key.channel());
- }
- }
-
- private 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);
- }
-
- private void read(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
- int readCount;
- PipeWorker pipe = _pipes.get(socketChannel);
- byte[] data;
-
- if (pipe == null) {
- // should not happen
- cleanUp(socketChannel);
- return;
- }
-
- _readBuffer.clear();
- try {
- readCount = socketChannel.read(_readBuffer);
- } catch (IOException e) {
- cleanUp(socketChannel);
- return;
- }
-
- if (readCount == -1) {
- cleanUp(socketChannel);
- return;
- }
-
- data = _readBuffer.array();
- pipe.processData(data, readCount, true);
- }
-
- private void write(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
-
- List queue = (List) _pendingData.get(socketChannel);
- if (queue != null) {
- synchronized (queue) {
- // Write data
- while (!queue.isEmpty()) {
- ByteBuffer buf = (ByteBuffer) queue.get(0);
- socketChannel.write(buf);
- if (buf.remaining() > 0) {
- break;
- }
- queue.remove(0);
- }
-
- if (queue.isEmpty()) {
- key.interestOps(SelectionKey.OP_READ);
- }
- }
- }
- else {
- logger.warning("LocalSocket::write queue = null: " + socketChannel);
- return;
- }
- }
-
- @Override
- protected void cleanUp(SocketChannel socketChannel) {
- //logger.warning("LocalSocket closed: " + socketChannel);
- super.cleanUp(socketChannel);
-
- PipeWorker pipe = _pipes.get(socketChannel);
- if (pipe != null) {
- pipe.close();
- _pipes.remove(socketChannel);
- logger.fine("LocalSocket closed: " + pipe.socketInfo);
- }
- else {
- logger.fine("LocalSocket closed (NULL): " + socketChannel);
- }
-
- }
-
- @Override
- public void close() {
- super.close();
- _executor.shutdownNow();
-
- try {
- _serverChannel.close();
- _remoteSocketHandler.close();
- } catch (IOException e) {
- logger.warning(Util.getErrorMessage(e));
- }
- logger.info("Server closed.");
- }
-}
diff --git a/src/main/java/com/stfl/network/nio/RemoteSocketHandler.java b/src/main/java/com/stfl/network/nio/RemoteSocketHandler.java
deleted file mode 100644
index 63820b7..0000000
--- a/src/main/java/com/stfl/network/nio/RemoteSocketHandler.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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 com.stfl.network.nio;
-
-import com.stfl.misc.Config;
-import com.stfl.misc.Util;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-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;
-import java.util.List;
-import java.util.logging.Logger;
-
-/**
- * Handler for processing all IO event for remote sockets
- */
-public class RemoteSocketHandler extends SocketHandlerBase {
- private Logger logger = Logger.getLogger(RemoteSocketHandler.class.getName());
-
- public RemoteSocketHandler(Config config) throws IOException, InvalidAlgorithmParameterException {
- super(config);
- }
-
- @Override
- protected Selector initSelector() throws IOException {
- return SelectorProvider.provider().openSelector();
- }
-
- @Override
- protected boolean processPendingRequest(ChangeRequest request) {
- if ((request.type != ChangeRequest.REGISTER_CHANNEL) && request.socket.isConnectionPending()) {
- return false;
- }
-
- SelectionKey key;
- switch (request.type) {
- case ChangeRequest.CHANGE_SOCKET_OP:
- key = request.socket.keyFor(_selector);
- if ((key != null) && key.isValid()) {
- key.interestOps(request.op);
- } else {
- logger.warning("RemoteSocketHandler::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.warning(e.toString());
- cleanUp(request.socket);
- }
- break;
- case ChangeRequest.CLOSE_CHANNEL:
- cleanUp(request.socket);
- break;
- }
-
- return true;
- }
-
- @Override
- protected void processSelect(SelectionKey key) {
- try {
- if (key.isConnectable()) {
- finishConnection(key);
- } else if (key.isReadable()) {
- read(key);
- } else if (key.isWritable()) {
- write(key);
- }
- } catch (IOException e) {
- cleanUp((SocketChannel) key.channel());
- }
- }
-
- 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;
- }
-
- private void read(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
- PipeWorker pipe = _pipes.get(socketChannel);
- if (pipe == null) {
- // should not happen
- cleanUp(socketChannel);
- return;
- }
-
- // clear read buffer for new data
- _readBuffer.clear();
-
- // read data
- int readCount;
- try {
- readCount = socketChannel.read(_readBuffer);
- } catch (IOException e) {
- // remote socket closed
- cleanUp(socketChannel);
-
- return;
- }
-
- if (readCount == -1) {
- cleanUp(socketChannel);
- return;
- }
-
- // Handle the response
- pipe.processData(_readBuffer.array(), readCount, false);
- }
-
- private void write(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
-
- List queue = (List) _pendingData.get(socketChannel);
- if (queue != null) {
- synchronized (queue) {
- // write data to socket
- while (!queue.isEmpty()) {
- ByteBuffer buf = (ByteBuffer) queue.get(0);
- socketChannel.write(buf);
- if (buf.remaining() > 0) {
- break;
- }
- queue.remove(0);
- }
-
- if (queue.isEmpty()) {
- key.interestOps(SelectionKey.OP_READ);
- }
- }
- }
- else {
- logger.warning("RemoteSocket::write queue = null: " + socketChannel);
- return;
- }
- }
-
- private void finishConnection(SelectionKey key) throws IOException {
- SocketChannel socketChannel = (SocketChannel) key.channel();
-
- try {
- socketChannel.finishConnect();
- } catch (IOException e) {
- logger.warning("RemoteSocketHandler::finishConnection I/O exception: " + e.toString());
- cleanUp(socketChannel);
- return;
- }
-
- key.interestOps(SelectionKey.OP_WRITE);
- }
-
- @Override
- protected void cleanUp(SocketChannel socketChannel) {
- super.cleanUp(socketChannel);
-
- PipeWorker pipe = _pipes.get(socketChannel);
- if (pipe != null) {
- pipe.close();
- _pipes.remove(socketChannel);
- logger.fine("RemoteSocket closed: " + pipe.socketInfo);
- }
- else {
- logger.fine("RemoteSocket closed (NULL): " + socketChannel);
- }
- }
-}
diff --git a/src/main/java/com/stfl/network/nio/SocketHandlerBase.java b/src/main/java/com/stfl/network/nio/SocketHandlerBase.java
deleted file mode 100644
index b5f585e..0000000
--- a/src/main/java/com/stfl/network/nio/SocketHandlerBase.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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 com.stfl.network.nio;
-
-import com.stfl.misc.Config;
-import com.stfl.misc.Util;
-import com.stfl.Constant;
-import com.stfl.network.IServer;
-import com.stfl.ss.CryptFactory;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedSelectorException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-import java.security.InvalidAlgorithmParameterException;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.logging.Logger;
-
-/**
- * Base class of socket handler for processing all IO event for sockets
- */
-public abstract class SocketHandlerBase implements IServer, ISocketHandler {
- private Logger logger = Logger.getLogger(SocketHandlerBase.class.getName());
- 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 abstract Selector initSelector() throws IOException;
- protected abstract boolean processPendingRequest(ChangeRequest request);
- protected abstract void processSelect(SelectionKey key);
-
-
- public SocketHandlerBase(Config config) throws IOException, InvalidAlgorithmParameterException {
- if (!CryptFactory.isCipherExisted(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.warning(Util.getErrorMessage(e));
- }
- }
- logger.fine(this.getClass().getName() + " Closed.");
- }
-
- protected void createWriteBuffer(SocketChannel socketChannel) {
- List queue = new ArrayList();
- Object put;
- put = _pendingData.putIfAbsent(socketChannel, queue);
- if (put != null) {
- logger.severe("Dup write buffer creation: " + socketChannel);
- }
- }
-
- protected void cleanUp(SocketChannel socketChannel) {
- try {
- socketChannel.close();
- } catch (IOException e) {
- logger.info(Util.getErrorMessage(e));
- }
- SelectionKey key = socketChannel.keyFor(_selector);
- if (key != null) {
- key.cancel();
- }
-
- if (_pendingData.containsKey(socketChannel)) {
- _pendingData.remove(socketChannel);
- }
- }
-
- @Override
- public void send(ChangeRequest request, byte[] data) {
- switch (request.type) {
- case ChangeRequest.CHANGE_SOCKET_OP:
- List queue = (List) _pendingData.get(request.socket);
- if (queue != null) {
- synchronized (queue) {
- // in general case, the write queue is always existed, unless, the socket has been shutdown
- queue.add(ByteBuffer.wrap(data));
- }
- }
- else {
- logger.warning(Util.getErrorMessage(new Throwable("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.warning(Util.getErrorMessage(e));
- }
- }
-}