作者 钟来

初始提交

正在显示 41 个修改的文件 包含 2465 行增加0 行删除
# shadowsocks-java
Shadowsocks-java 是一个基于SOCKS5代理的使用java开发的[shadowsocks](https://github.com/shadowsocks/shadowsocks)
代理软件。可以同时作为客户端和服务端使用目前只支持TCP协议及流加密,后续会增加UDP协议和AEAD的支持。
## Build & Install
```bash
git clone https://github.com/zhihengjiang/shasowsocks-java
cd shadowsocks-java
mvn package
```
## Getting Started
创建配置文件
```json
{
"server": "my_server_ip",
"server_port": 8388,
"local_address": "127.0.0.1",
"local_port": 1080,
"password": "mypassword",
"timeout": 300,
"method": "aes-256-cfb"
}
```
详细的参数解释可以参考 [shadowsocks](https://github.com/shadowsocks/shadowsocks/wiki) 的文档.
在本地使用
```bash
chmod +x ./sslocal.sh
./sslocal.sh -c config.json
```
在服务器端使用
```bash
chmod +x ./ssserver.sh
./ssserver.sh -c config.json
```
更多脚本参数请输入
```bash
./ssserver.sh -h
./sslocal.sh -h
```
## 支持的加密方式
### 流加密
* `aes-128-cfb`, `aes-192-cfb`, `aes-256-cfb`
* `aes-128-ofb`, `aes-192-ofb`, `aes-256-ofb`
* `chacha20`, `chacha20-ietf`
## TODO
- [ ] Documentation
- [ ] 支持UDP协议
- [ ] 支持AEAD加密
- [ ] 编写使用脚本
... ...
{
"server":"127.0.0.1",
"server_port":8000,
"local_address": "127.0.0.1",
"local_port":1080,
"password":"123456",
"timeout":300,
"method":"aes-256-cfb",
"fast_open": false,
"workers": 1
}
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.shadowsocks</groupId>
<artifactId>shadowsocks-java</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 支持data -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.7.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.56</version>
</dependency>
<dependency>
<groupId>gnu.getopt</groupId>
<artifactId>java-getopt</artifactId>
<version>1.0.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
<version>4.5.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<skip>true</skip>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix><!--指定classpath的前缀-->
<mainClass>org.shadowsocks.Main</mainClass><!--指定主类的类名-->
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!--指定outputDirectory-->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
... ...
package org.shadowsocks;
import org.shadowsocks.config.Config;
import org.shadowsocks.config.JsonConfig;
import org.shadowsocks.util.CommandLineParser;
import java.util.Arrays;
public class Main {
public static void main( String[] args) throws Exception{
if(null == args || args.length == 0)
{
args = new String[]{"ServerMain"};
}
String path = System.getProperty("user.dir") + "/config.json";
Config config = new JsonConfig(path);
String main = args[0];
// Config config = CommandLineParser.parse(Arrays.copyOfRange(args,1,args.length));
switch (main){
case "LocalMain":
new ShadowsocksLocal(config).start();break;
case "ServerMain":
new ShadowSocksServer(config).start();break;
default:System.out.println("please set running mode");break;
}
}
}
... ...
package org.shadowsocks;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpServerCodec;
import org.shadowsocks.config.Config;
import org.shadowsocks.crypto.CryptoFactory;
import org.shadowsocks.handler.server.AddressHandler;
import org.shadowsocks.handler.server.StringHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ShadowSocksServer {
private static Logger logger = LoggerFactory.getLogger(ShadowSocksServer.class);
Config config;
public ShadowSocksServer(Config config){
this.config = config;
}
public void start() throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup(2);
ServerBootstrap bootstrap = new ServerBootstrap();
try {
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(config.getServerPort())
.option(ChannelOption.SO_TIMEOUT, config.getTimeout())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new StringHandler(CryptoFactory.create(config.getMethod(),
config.getPassword())));
}
});
ChannelFuture channelFuture = bootstrap.bind().sync();
logger.info("started and listen on " + channelFuture.channel().localAddress());
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
... ...
package org.shadowsocks;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5ServerEncoder;
import org.shadowsocks.config.Config;
import org.shadowsocks.handler.local.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ShadowsocksLocal {
private static Logger logger = LoggerFactory.getLogger(ShadowSocksServer.class);
private Config config;
public ShadowsocksLocal(Config config){
this.config = config;
}
public void start() throws InterruptedException{
EventLoopGroup worker = new NioEventLoopGroup(1);
EventLoopGroup boss = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
try{
bootstrap.group(worker,boss)
.localAddress(config.getLocalPort())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(Socks5ServerEncoder.DEFAULT);
ch.pipeline().addLast(new Socks5InitialRequestDecoder());
ch.pipeline().addLast(new Socks5InitialRequestHandler());
ch.pipeline().addLast(new Socks5CommandRequestDecoder());
ch.pipeline().addLast(new Socks5CmdRequesthandler(config));
}
});
ChannelFuture futrue = bootstrap.bind().sync();
logger.info("connected local port:" + config.getLocalPort());
futrue.channel().closeFuture().sync();
}
finally {
worker.shutdownGracefully();
boss.shutdownGracefully();
}
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.auth;
public class AuthException extends Exception
{
private static final long serialVersionUID = 1L;
public AuthException(String message){
super(message);
}
public AuthException(Exception e){
super(e);
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.auth;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class HmacSHA1 extends SSAuth{
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public static final int AUTH_LEN = 10;
@Override
public byte[] doAuth(byte[] key, byte [] data) throws AuthException
{
try{
SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte [] original_result;
byte [] result = new byte[AUTH_LEN];
original_result = mac.doFinal(data);
System.arraycopy(original_result, 0, result, 0, AUTH_LEN);
return result;
}catch(NoSuchAlgorithmException | InvalidKeyException e){
throw new AuthException(e);
}
}
@Override
public boolean doAuth(byte[] key, byte [] data, byte [] expect) throws AuthException
{
return Arrays.equals(expect, doAuth(key, data));
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.auth;
import java.nio.ByteBuffer;
/**
* Auth base class
*/
public abstract class SSAuth{
public abstract byte [] doAuth(byte[] key, byte[] data) throws AuthException;
public abstract boolean doAuth(byte[] key, byte[] data, byte[] auth) throws AuthException;
public static byte [] prepareKey(byte [] i, int c){
byte [] key = new byte[i.length + 4];
ByteBuffer b = ByteBuffer.allocate(4);
b.putInt(c);
System.arraycopy(i, 0, key, 0, i.length);
System.arraycopy(b.array(), 0, key, i.length, 4);
return key;
}
public static byte [] prepareKey(byte [] i, byte [] k){
byte [] key = new byte[i.length + k.length];
System.arraycopy(i, 0, key, 0, i.length);
System.arraycopy(k, 0, key, i.length, k.length);
return key;
}
}
... ...
package org.shadowsocks.config;
public abstract class BaseConfig implements Config{
private RealConfig config;
public BaseConfig(Object source){
this.config = loadConfig(source);
}
@Override
public String getServerAddress() {
return config.server;
}
@Override
public int getServerPort() {
return config.server_port;
}
@Override
public String getLocalAddress() {
return config.local_address;
}
@Override
public int getLocalPort() {
return config.local_port;
}
@Override
public int getTimeout() {
return config.timeout;
}
@Override
public String getMethod() {
return config.method;
}
@Override
public String getPassword() {
return config.password;
}
public RealConfig getConfig(){
return this.config;
}
}
... ...
package org.shadowsocks.config;
public interface Config {
String getServerAddress();
int getServerPort();
int getLocalPort();
String getLocalAddress();
String getPassword();
int getTimeout();
String getMethod();
RealConfig loadConfig(Object source);
}
... ...
package org.shadowsocks.config;
public class ConfigFactory {
public static Config getConfig(String path){
return new JsonConfig(path);
}
}
... ...
package org.shadowsocks.config;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import java.io.*;
public class JsonConfig extends BaseConfig {
public JsonConfig(String path){
super(path);
}
@Override
public RealConfig loadConfig(Object path) {
Gson gson = new Gson();
RealConfig config = new RealConfig();
try{
JsonReader reader = new JsonReader(new FileReader((String)path));
config = gson.fromJson(reader,RealConfig.class);
}
catch (IOException e){
e.printStackTrace();
}
return config;
}
public static void main(String[] args){
String path = "/home/thales/config.json";
Config config = new JsonConfig(path);
System.out.println(new Gson().toJsonTree(((JsonConfig) config).getConfig()));
}
}
... ...
package org.shadowsocks.config;
public class PropertiesConfig extends BaseConfig{
public PropertiesConfig(String path){
super(path);
}
@Override
public RealConfig loadConfig(Object path) {
return new RealConfig();
}
}
... ...
package org.shadowsocks.config;
public class RealConfig {
public String server;
public int server_port;
public String local_address;
public int local_port;
public int timeout;
public String method;
public String password;
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.modes.OFBBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import java.io.ByteArrayOutputStream;
/**
* AES Crypt implementation
*/
public class AESCrypto extends BaseCrypto {
public final static String CIPHER_AES_128_CFB = "aes-128-cfb";
public final static String CIPHER_AES_192_CFB = "aes-192-cfb";
public final static String CIPHER_AES_256_CFB = "aes-256-cfb";
public final static String CIPHER_AES_128_OFB = "aes-128-ofb";
public final static String CIPHER_AES_192_OFB = "aes-192-ofb";
public final static String CIPHER_AES_256_OFB = "aes-256-ofb";
private final static int IV_LENGTH = 16;
public AESCrypto(String name, String password) throws CryptoException {
super(name, password);
}
@Override
public int getIVLength() {
return IV_LENGTH;
}
@Override
public int getKeyLength() {
if(mName.equals(CIPHER_AES_128_CFB) || mName.equals(CIPHER_AES_128_OFB)) {
return 16;
}
else if (mName.equals(CIPHER_AES_192_CFB) || mName.equals(CIPHER_AES_192_OFB)) {
return 24;
}
else if (mName.equals(CIPHER_AES_256_CFB) || mName.equals(CIPHER_AES_256_OFB)) {
return 32;
}
return 0;
}
protected StreamBlockCipher getCipher(boolean isEncrypted) throws CryptoException
{
AESEngine engine = new AESEngine();
StreamBlockCipher cipher;
if (mName.equals(CIPHER_AES_128_CFB)) {
cipher = new CFBBlockCipher(engine, getIVLength() * 8);
}
else if (mName.equals(CIPHER_AES_192_CFB)) {
cipher = new CFBBlockCipher(engine, getIVLength() * 8);
}
else if (mName.equals(CIPHER_AES_256_CFB)) {
cipher = new CFBBlockCipher(engine, getIVLength() * 8);
}
else if (mName.equals(CIPHER_AES_128_OFB)) {
cipher = new OFBBlockCipher(engine, getIVLength() * 8);
}
else if (mName.equals(CIPHER_AES_192_OFB)) {
cipher = new OFBBlockCipher(engine, getIVLength() * 8);
}
else if (mName.equals(CIPHER_AES_256_OFB)) {
cipher = new OFBBlockCipher(engine, getIVLength() * 8);
}
else {
throw new CryptoException("Invalid AlgorithmParameter: " + mName);
}
return cipher;
}
@Override
protected StreamCipher createCipher(byte[] iv, boolean encrypt) throws CryptoException
{
StreamBlockCipher c = getCipher(encrypt);
ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(mKey), iv, 0, mIVLength);
c.init(encrypt, parameterIV);
return c;
}
@Override
protected void process(byte[] in, ByteArrayOutputStream out, boolean encrypt){
int size;
byte[] buffer = new byte[in.length];
StreamBlockCipher cipher;
if (encrypt){
cipher = (StreamBlockCipher)mEncryptCipher;
}else{
cipher = (StreamBlockCipher)mDecryptCipher;
}
size = cipher.processBytes(in, 0, in.length, buffer, 0);
out.write(buffer, 0, size);
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
import org.bouncycastle.crypto.StreamCipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* Crypt base class implementation
*/
public abstract class BaseCrypto implements SSCrypto
{
protected abstract StreamCipher createCipher(byte[] iv, boolean encrypt) throws CryptoException;
protected abstract void process(byte[] in, ByteArrayOutputStream out, boolean encrypt);
protected final String mName;
protected final byte[] mKey;
protected final int mIVLength;
protected final int mKeyLength;
protected StreamCipher mEncryptCipher = null;
protected StreamCipher mDecryptCipher = null;
protected byte[] mEncryptIV;
protected byte[] mDecryptIV;
// One SSCrypto could only do one decrypt/encrypt at the same time.
protected ByteArrayOutputStream mData;
private final byte [] mLock = new byte[0];
public BaseCrypto(String name, String password) throws CryptoException
{
mName = name.toLowerCase();
mIVLength = getIVLength();
mKeyLength = getKeyLength();
if (mKeyLength == 0) {
throw new CryptoException("Unsupport method: " + mName);
}
mKey = Utils.getKey(password, mKeyLength, mIVLength);
mData = new ByteArrayOutputStream();
}
public byte [] getKey(){
return mKey;
}
public byte [] getIV(boolean encrypt){
if (encrypt){
if (mEncryptIV == null){
mEncryptIV = Utils.randomBytes(mIVLength);
}
return mEncryptIV;
}else
return mDecryptIV;
}
private byte [] encryptLocked(byte[] in) throws CryptoException
{
mData.reset();
if (mEncryptCipher == null) {
mEncryptIV = getIV(true);
mEncryptCipher = createCipher(mEncryptIV, true);
try {
mData.write(mEncryptIV);
} catch (IOException e) {
throw new CryptoException(e);
}
}
process(in, mData, true);
return mData.toByteArray();
}
@Override
public byte [] encrypt(byte[] in, int length) throws CryptoException
{
synchronized(mLock) {
if (length != in.length){
byte[] data = new byte[length];
System.arraycopy(in, 0, data, 0, length);
return encryptLocked(data);
}else{
return encryptLocked(in);
}
}
}
private byte[] decryptLocked(byte[] in) throws CryptoException
{
byte[] data;
mData.reset();
if (mDecryptCipher == null) {
mDecryptCipher = createCipher(in, false);
mDecryptIV = new byte[mIVLength];
data = new byte[in.length - mIVLength];
System.arraycopy(in, 0, mDecryptIV, 0, mIVLength);
System.arraycopy(in, mIVLength, data, 0, in.length - mIVLength);
} else {
data = in;
}
process(data, mData, false);
return mData.toByteArray();
}
@Override
public byte [] decrypt(byte[] in, int length) throws CryptoException
{
synchronized(mLock) {
if (length != in.length) {
byte[] data = new byte[length];
System.arraycopy(in, 0, data, 0, length);
return decryptLocked(data);
}else{
return decryptLocked(in);
}
}
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.engines.ChaCha7539Engine;
import org.bouncycastle.crypto.engines.ChaChaEngine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import java.io.ByteArrayOutputStream;
/**
* Chacha20 Crypt implementation
*/
public class Chacha20Crypto extends BaseCrypto {
private final static String CIPHER_CHACHA20 = "chacha20";
private final static String CIPHER_CHACHA20_IETF = "chacha20-ietf";
private final static int IV_LENGTH = 8;
private final static int IV_IETF_LENGTH = 12;
private final static int KEY_LENGTH = 32;
public Chacha20Crypto(String name, String password) throws CryptoException {
super(name, password);
}
@Override
public int getIVLength() {
if (mName.equals(CIPHER_CHACHA20_IETF)) {
return IV_IETF_LENGTH;
} else {
return IV_LENGTH;
}
}
@Override
public int getKeyLength() {
if (mName.equals(CIPHER_CHACHA20) || mName.equals(CIPHER_CHACHA20_IETF)) {
return KEY_LENGTH;
}
return 0;
}
@Override
protected StreamCipher createCipher(byte[] iv, boolean encrypt) throws CryptoException
{
StreamCipher c;
if (mName.equals(CIPHER_CHACHA20_IETF)) {
c = new ChaCha7539Engine();
} else {
c = new ChaChaEngine();
}
ParametersWithIV parameterIV = new ParametersWithIV(new KeyParameter(mKey), iv, 0, mIVLength);
c.init(encrypt, parameterIV);
return c;
}
@Override
protected void process(byte[] in, ByteArrayOutputStream out, boolean encrypt){
int size;
byte[] buffer = new byte[in.length];
StreamCipher cipher;
if (encrypt){
cipher = mEncryptCipher;
}else{
cipher = mDecryptCipher;
}
size = cipher.processBytes(in, 0, in.length, buffer, 0);
out.write(buffer, 0, size);
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
public class CryptoException extends Exception
{
private static final long serialVersionUID = 1L;
public CryptoException(String message){
super(message);
}
public CryptoException(Exception e){
super(e);
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
public class CryptoFactory{
private static final String AES = "aes";
private static final String CHACHA20 = "chacha20";
public static SSCrypto create(String name, String password) throws CryptoException
{
String cipherName = name.toLowerCase();
if (cipherName.startsWith(AES)) {
return new AESCrypto(name, password);
}else if (cipherName.startsWith(CHACHA20)) {
return new Chacha20Crypto(name, password);
}else{
throw new CryptoException("Unsupport method: " + name);
}
}
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
/**
* Interface of crypt
*/
public interface SSCrypto {
byte [] encrypt(byte[] data, int length) throws CryptoException;
byte [] decrypt(byte[] data, int length) throws CryptoException;
int getIVLength();
int getKeyLength();
byte [] getIV(boolean encrypt);
byte [] getKey();
}
... ...
/*
* Copyright 2016 Author:NU11 bestoapache@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.shadowsocks.crypto;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class Utils{
/**
* Thanks go to Ola Bini for releasing this source on his blog.
* The source was obtained from <a href="http://olabini.com/blog/tag/evp_bytestokey/">here</a> .
*/
private static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data, int count) {
byte[][] both = new byte[2][];
byte[] key = new byte[key_len];
int key_ix = 0;
byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if (data == null) {
return both;
}
int addmd = 0;
for (;;) {
md.reset();
if (addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if (null != salt) {
md.update(salt, 0, 8);
}
md_buf = md.digest();
for (i = 1; i < count; i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i = 0;
if (nkey > 0) {
for (;;) {
if (nkey == 0)
break;
if (i == md_buf.length)
break;
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if (niv > 0 && i != md_buf.length) {
for (;;) {
if (niv == 0)
break;
if (i == md_buf.length)
break;
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if (nkey == 0 && niv == 0) {
break;
}
}
for (i = 0; i < md_buf.length; i++) {
md_buf[i] = 0;
}
return both;
}
public static byte[] getKey(String password, int keyLen, int ivLen) throws CryptoException
{
MessageDigest md = null;
byte[] passwordBytes = null;
byte[][] keyAndIV = null;
int i = 0;
try{
md = MessageDigest.getInstance("MD5");
passwordBytes = password.getBytes("ASCII");
}catch(NoSuchAlgorithmException | UnsupportedEncodingException e){
throw new CryptoException(e);
}
//This key should equal EVP_BytesToKey with no salt and count = 1
keyAndIV = EVP_BytesToKey(keyLen, ivLen, md, null, passwordBytes, 1);
//Discard the iv.
return keyAndIV[0];
}
public static byte[] randomBytes(int size) {
byte[] bytes = new byte[size];
new SecureRandom().nextBytes(bytes);
return bytes;
}
}
... ...
package org.shadowsocks.dto;
import lombok.Data;
@Data
public class RequestInfo {
private String host;
private int post;
public RequestInfo(String host,int post)
{
this.host = host;
this.post = post;
}
}
... ...
package org.shadowsocks.dto;
public enum RequestMethod {
/**
* 从服务器获取一份文档
*
* 请求实体(不支持)
*
* 响应实体(支持)
*/
GET,
/**
* 向服务器发送需要处理的数据
*
* 请求实体(支持)
*
* 响应实体(支持)
*/
POST,
/**
* 只从服务器获取文档的首部
*
* 请求实体(不支持)
*
* 响应实体(不支持)
*/
HEAD,
/**
* 将请求的主体部分存储在服务器上
*
* 请求实体(支持)
*
* 响应实体(支持)
*/
PUT,
/**
* 对可能经过代理服务器传送到服务器上去的报文进行追踪
*
* 请求实体(不支持)
*
* 响应实体(支持)
*/
TRACE,
/**
* 决定可以在服务器上执行哪些方法
*
* 请求实体(不支持)
*
* 响应实体(不支持)
*/
OPTIONS,
/**
* 从服务器上删除一份文档
*
* 请求实体(不支持)
*
* 响应实体(支持)
*/
DELETE,
/**
* 用于https
*
* 请求实体(不支持)
*
* 响应实体(支持)
*/
CONNECT;
}
... ...
package org.shadowsocks.handler.local;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandRequest;
import org.shadowsocks.crypto.SSCrypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.IDN;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class Local2RemoteHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(Local2RemoteHandler.class);
private ChannelFuture destChannelFuture;
private SSCrypto ssCrypto;
private DefaultSocks5CommandRequest socks5CommandRequest;
private boolean isProxy = true;
private boolean addAddress = false;
public Local2RemoteHandler(ChannelFuture destChannelFuture,SSCrypto ssCrypto, DefaultSocks5CommandRequest msg,
boolean isProxy) {
this.destChannelFuture = destChannelFuture;
this.ssCrypto = ssCrypto;
this.socks5CommandRequest = msg;
this.isProxy = isProxy;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(isProxy){
ByteBuf buff = (ByteBuf)msg;
if(!addAddress){
ByteBuf addressInfo = parseAddress(socks5CommandRequest);
addressInfo.writeBytes(buff);
buff = addressInfo;
addAddress = true;
}
byte[] plainTxt = ByteBufUtil.getBytes(buff);
byte[] encrypt = ssCrypto.encrypt(plainTxt,plainTxt.length);
logger.info("relay message to remote server from client");
destChannelFuture.channel().writeAndFlush(Unpooled.copiedBuffer(encrypt));
}
else {
destChannelFuture.channel().writeAndFlush(msg);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.trace("close connection");
destChannelFuture.channel().close();
}
private ByteBuf parseAddress (DefaultSocks5CommandRequest msg) throws Exception{
ByteBuf buff = Unpooled.buffer();
byte addressType = msg.dstAddrType().byteValue();
int port = msg.dstPort();
String host = msg.dstAddr();
if(addressType == 0x01){
buff.writeByte(0x01);
InetAddress address = Inet4Address.getByName(host);
byte[] addr = address.getAddress();
buff.writeBytes(addr);
}
if(addressType == 0x03){
buff.writeByte(0x03);
String address = IDN.toASCII(host);
byte[] addr = address.getBytes(StandardCharsets.US_ASCII);
System.out.println(address+","+host+","+addr.length+","+Arrays.toString(addr));
buff.writeByte(addr.length);
buff.writeBytes(addr);
}
else
throw new IllegalArgumentException("IP v6 not supported");
buff.writeShort(port);
return buff;
}
}
\ No newline at end of file
... ...
package org.shadowsocks.handler.local;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.shadowsocks.crypto.SSCrypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class Remote2LocalHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(Remote2LocalHandler.class);
private ChannelHandlerContext clientChannelContext;
private SSCrypto ssCrypto;
private boolean isProxy = true;
public Remote2LocalHandler(ChannelHandlerContext clientChannelContext, SSCrypto ssCrypto, boolean isProxy) {
this.clientChannelContext = clientChannelContext;
this.ssCrypto = ssCrypto;
this.isProxy = isProxy;
}
@Override
public void channelRead(ChannelHandlerContext ctx2, Object remoteMsg) throws Exception {
if(isProxy){
ByteBuf buff = (ByteBuf)remoteMsg;
byte[] encrypted = ByteBufUtil.getBytes(buff);
byte[] decrypted = ssCrypto.decrypt(encrypted,encrypted.length);
logger.info("relay response of target server to client");
// System.out.println("========="+Unpooled.copiedBuffer(decrypted).toString(StandardCharsets.US_ASCII));
clientChannelContext.writeAndFlush(Unpooled.copiedBuffer(decrypted));
}
else {
clientChannelContext.writeAndFlush(remoteMsg);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx2) throws Exception {
logger.trace("close connection to target server");
clientChannelContext.channel().close();
}
}
... ...
package org.shadowsocks.handler.local;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.socksx.v5.*;
import org.shadowsocks.config.Config;
import org.shadowsocks.crypto.CryptoFactory;
import org.shadowsocks.crypto.SSCrypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* handle socks5 request
*/
public class Socks5CmdRequesthandler extends SimpleChannelInboundHandler<DefaultSocks5CommandRequest> {
private static final Logger logger = LoggerFactory.getLogger(Socks5CmdRequesthandler.class);
private Config config;
private EventLoopGroup bossGroup = new NioEventLoopGroup();
private SSCrypto ssCrypto ;
private boolean isProxy = true;
public Socks5CmdRequesthandler(Config config){
this.config = config;
try{
ssCrypto = CryptoFactory.create(config.getMethod(),config.getPassword());
}
catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DefaultSocks5CommandRequest msg) {
if(msg.type().equals(Socks5CommandType.CONNECT)) {
logger.trace("connecting remote server");
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(bossGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new Remote2LocalHandler(ctx,ssCrypto,isProxy));
}
});
ChannelFuture future;
if(isProxy){
future = bootstrap.connect(config.getServerAddress(), config.getServerPort());
}
else {
future = bootstrap.connect(msg.dstAddr(),msg.dstPort());
}
future.addListener(new ChannelFutureListener() {
public void operationComplete(final ChannelFuture future) throws Exception {
if(future.isSuccess()) {
logger.info("successfully connected remote server");
ctx.pipeline().addLast(new Local2RemoteHandler(future,ssCrypto,msg,isProxy));
Socks5CommandResponse commandResponse = new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4);
ctx.writeAndFlush(commandResponse);
} else {
Socks5CommandResponse commandResponse = new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, Socks5AddressType.IPv4);
ctx.writeAndFlush(commandResponse);
}
}
});
} else {
ctx.fireChannelRead(msg);
}
}
}
... ...
package org.shadowsocks.handler.local;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.socksx.SocksVersion;
import io.netty.handler.codec.socksx.v5.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Socks5InitialRequestHandler extends SimpleChannelInboundHandler<DefaultSocks5InitialRequest> {
private static final Logger logger = LoggerFactory.getLogger(Socks5InitialRequestHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, DefaultSocks5InitialRequest msg) {
if(msg.decoderResult().isFailure()) {
logger.warn("current protocol is not socks5");
ctx.fireChannelRead(msg);
} else {
if(msg.version().equals(SocksVersion.SOCKS5)) {
Socks5InitialResponse initialResponse = new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH);
ctx.writeAndFlush(initialResponse);
System.out.println(initialResponse);
}
}
// ctx.pipeline().remove(Socks5InitialRequestDecoder.class);
}
}
... ...
package org.shadowsocks.handler.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.shadowsocks.crypto.SSCrypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
public class AddressHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(AddressHandler.class);
private final static int ADDR_TYPE_IPV4 = 1;
private final static int ADDR_TYPE_HOST = 3;
private final ByteBuf dataQueue = Unpooled.buffer();
private final SSCrypto ssCrypto;
public AddressHandler(SSCrypto ssCrypto) {
this.ssCrypto = ssCrypto;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("connected with {}", ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("disconnected with {}", ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buff = (ByteBuf) msg;
if (buff.readableBytes() <= 0) {
return;
}
byte [] array = ByteBufUtil.getBytes(buff);
logger.info("http内容\n"+new String(array));
dataQueue.writeBytes(array);
if (dataQueue.readableBytes() < 2) {
return;
}
String host = null;
int port = 0;
int addressType = dataQueue.getUnsignedByte(0);
if (addressType == ADDR_TYPE_IPV4) {
if (dataQueue.readableBytes() < 7) {
return;
}
// addrType(1) + ipv4(4) + port(2)
dataQueue.readUnsignedByte();
byte[] ipBytes = new byte[4];
dataQueue.readBytes(ipBytes);
host = InetAddress.getByAddress(ipBytes).toString().substring(1);
port = dataQueue.readShort();
} else if (addressType == ADDR_TYPE_HOST) {
int hostLength = dataQueue.getUnsignedByte(1);
if (dataQueue.readableBytes() < hostLength + 4) {
return;
}
dataQueue.readUnsignedByte();
dataQueue.readUnsignedByte();
byte[] hostBytes = new byte[hostLength];
dataQueue.readBytes(hostBytes);
host = new String(hostBytes);
port = dataQueue.readShort();
} else {
throw new IllegalStateException("unknown address type: " + addressType);
}
ctx.channel().pipeline().addLast(new ClientDataHandler(host, port, ctx, dataQueue, ssCrypto));
ctx.channel().pipeline().remove(this);
}
}
... ...
package org.shadowsocks.handler.server;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.shadowsocks.crypto.SSCrypto;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
public class ClientDataHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(ClientDataHandler.class);
private final SSCrypto ssCrypto;
private final AtomicReference<Channel> remoteChannel = new AtomicReference<>();
private final ByteBuf clientCache;
public ClientDataHandler(String host, int port, ChannelHandlerContext clientCtx, ByteBuf clientCache, SSCrypto ssCrypto) {
this.ssCrypto = ssCrypto;
this.clientCache = clientCache;
init(host, port, clientCtx, clientCache, ssCrypto);
}
private void init(String host, int port, final ChannelHandlerContext clientCtx, final ByteBuf byteBuffer, final SSCrypto ssCrypto) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(clientCtx.channel().eventLoop())
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5 * 1000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new RemoteDataHandler(clientCtx, ssCrypto, byteBuffer));
}
});
try {
ChannelFuture channelFuture = bootstrap.connect(InetAddress.getByName(host), port);
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
logger.info("successfully to connect to {}:{}", host, port);
remoteChannel.set(future.channel());
} else {
logger.info("error to connect to {}:{}", host, port);
clientCtx.close();
}
}
});
} catch (Exception e) {
e.printStackTrace();
clientCtx.close();
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buff = (ByteBuf) msg;
if (buff.readableBytes() <= 0) {
return;
}
byte[] bytes = ByteBufUtil.getBytes(buff);
byte[] decrypt = ssCrypto.decrypt(bytes, bytes.length);
if(remoteChannel.get() == null) {
clientCache.writeBytes(decrypt);
} else {
remoteChannel.get().writeAndFlush(Unpooled.copiedBuffer(decrypt));
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.close();
if(remoteChannel.get() != null){
remoteChannel.get().close();
}
}
public static class RemoteDataHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final ChannelHandlerContext clientCtx;
private final SSCrypto ssCrypto;
private final ByteBuf byteBuffer;
public RemoteDataHandler(ChannelHandlerContext clientCtx, SSCrypto ssCrypto, ByteBuf byteBuffer) {
this.clientCtx = clientCtx;
this.ssCrypto = ssCrypto;
this.byteBuffer = byteBuffer;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("!!!!!!!!!!!!!!!!!!!!:"+new String(ByteBufUtil.getBytes(byteBuffer),StandardCharsets.UTF_8));
ctx.writeAndFlush(byteBuffer);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
byte[] bytes = ByteBufUtil.getBytes(msg);
try {
// byte[] encrypt = ssCrypto.encrypt(bytes, bytes.length);
System.out.println("++++++++++++++++:\n"+new String(bytes,StandardCharsets.UTF_8));
clientCtx.writeAndFlush(Unpooled.copiedBuffer(bytes));
} catch (Exception e) {
ctx.close();
clientCtx.close();
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.close();
clientCtx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
clientCtx.close();
}
}
}
... ...
package org.shadowsocks.handler.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.shadowsocks.crypto.SSCrypto;
import org.shadowsocks.dto.RequestInfo;
import org.shadowsocks.dto.RequestMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import static org.shadowsocks.dto.RequestMethod.CONNECT;
public class StringHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(StringHandler.class);
private final ByteBuf dataQueue = Unpooled.buffer();
private final SSCrypto ssCrypto;
public StringHandler(SSCrypto ssCrypto) {
this.ssCrypto = ssCrypto;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("connected with {}", ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("disconnected with {}", ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buff = (ByteBuf) msg;
if (buff.readableBytes() <= 0) {
return;
}
byte [] array = ByteBufUtil.getBytes(buff);
logger.info("请求内容\n{}\n{}",msg.getClass(),new String(array));
dataQueue.writeBytes(array);
if (dataQueue.readableBytes() < 2) {
return;
}
byte[] ats = ByteBufUtil.getBytes(dataQueue.slice(dataQueue.readerIndex(),dataQueue.bytesBefore((byte) ' ')));
String addressType = new String(ats);
RequestInfo requestInfo = getRequestInfo(RequestMethod.valueOf(addressType));
if(null != requestInfo)
{
ctx.channel().pipeline().addLast(new ClientDataHandler(requestInfo.getHost(), requestInfo.getPost(), ctx, buff,ssCrypto));
ctx.channel().pipeline().remove(this);
}else{
rterre(ctx,"没有解析到头部信息");
}
}
private void rterre(ChannelHandlerContext ctx,String message)
{
//构造内容
ByteBuf context = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
//设置 response
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
context);
//构建 响应头
HttpHeaders headers = response.headers();
headers.set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
headers.set(HttpHeaderNames.CONTENT_LENGTH, context.readableBytes());
//将消息返回
ctx.channel().writeAndFlush(response);
}
private RequestInfo getRequestInfo(RequestMethod requestMethod) throws UnknownHostException {
switch (requestMethod)
{
case CONNECT:
if (dataQueue.readableBytes() < 7) {
return null;
}
// addrType(7) + ipv4(4) + port(2)
dataQueue.readerIndex(7);
dataQueue.readUnsignedByte();
int index = dataQueue.bytesBefore((byte) ' ');
byte[] ats = new byte[index];
dataQueue.readBytes(ats);
String[] addresAndPost = new String(ats).split(":");
dataQueue.readUnsignedByte();
return new RequestInfo(addresAndPost[0],Integer.parseInt(addresAndPost[1]));
// case ADDR_TYPE_HOST:
// int hostLength = dataQueue.getUnsignedByte(1);
// if (dataQueue.readableBytes() < hostLength + 4) {
// return null;
// }
// dataQueue.readUnsignedByte();
// dataQueue.readUnsignedByte();
// byte[] hostBytes = new byte[hostLength];
// dataQueue.readBytes(hostBytes);
// return new RequestInfo(new String(hostBytes),dataQueue.readShort());
default:
throw new IllegalStateException("unknown address type: " + requestMethod);
}
}
}
... ...
package org.shadowsocks.util;
import org.shadowsocks.config.BaseConfig;
import org.shadowsocks.config.Config;
import org.shadowsocks.config.JsonConfig;
import org.shadowsocks.config.RealConfig;
import java.util.Arrays;
public class CommandLineParser {
public static Config parse(String[] args)
{
RealConfig realConfig = new RealConfig();
for(int i=0;i<args.length;i++)
{
System.out.println(Arrays.toString(args));
String[] parts = args[i].split("=");
String key = parts[0];
String value = parts[1];
if(key.compareTo("config")==0)
return new JsonConfig(value);
if(key.compareTo("server")==0)
realConfig.server = value;
if(key.compareTo("server_port")==0)
realConfig.server_port = Integer.parseInt(value);
if(key.compareTo("local_address")==0)
realConfig.local_address = value;
if(key.compareTo("local_port")==0)
realConfig.local_port = Integer.parseInt(value);
if(key.compareTo("method")==0)
realConfig.method = value;
if(key.compareTo("password")==0)
realConfig.password = value;
if(key.compareTo("time_out")==0)
realConfig.timeout = Integer.parseInt(value);
}
return new BaseConfig(realConfig) {
@Override
public RealConfig loadConfig(Object source) {
return (RealConfig) source;
}
};
}
}
... ...
<configuration>
<timestamp key="byDay" datePattern="yyyyMMdd'T'HHmmss"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/org.shadowsocks-log-${byDay}.txt </file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
... ...
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
<id>bin</id>
<!-- 最终打包成一个用于发布的zip文件 -->
<formats>
<format>zip</format>
</formats>
<!-- Adds dependencies to zip package under lib directory -->
<dependencySets>
<dependencySet>
<!--
不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录
-->
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
<fileSets>
<!-- 把项目相关的说明文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
<include>LICENSE*</include>
<include>NOTICE*</include>
</includes>
</fileSet>
<!-- 把项目的配置文件,打包进zip文件的config目录 -->
<fileSet>
<directory>${project.basedir}\src\main\resources\configs</directory>
<outputDirectory>../configs</outputDirectory>
<includes>
<include>*.properties</include>
</includes>
</fileSet>
<!-- 把项目的配置文件,提出来 -->
<fileSet>
<directory>${project.basedir}\src\main\resources</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>*.properties</include>
<include>*.yml</include>
</includes>
</fileSet>
<!-- 把项目的脚本文件目录( src/main/scripts )中的启动脚本文件,打包进zip文件的跟目录 -->
<fileSet>
<directory>${project.basedir}\bin</directory>
<outputDirectory></outputDirectory>
<includes>
<include>start.*</include>
<include>stop.*</include>
</includes>
</fileSet>
<!-- 把项目自己编译出来的jar文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
\ No newline at end of file
... ...
package org.shadowsocks.Util;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpUtil {
private final String USER_AGENT = "Mozilla/5.0";
// public static void main(String[] args) throws Exception {
//
// HttpURLConnectionExample http = new HttpURLConnectionExample();
//
// System.out.println("Testing 1 - Send Http GET request");
// http.sendGet();
//
// System.out.println("\nTesting 2 - Send Http POST request");
// http.sendPost();
//
// }
// HTTP GET请求
public void sendGet() throws Exception {
String url = "http://www.baidu.com/search?q=mkyong";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
//默认值我GET
con.setRequestMethod("GET");
//添加请求头
con.setRequestProperty("User-Agent", USER_AGENT);
int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//打印结果
System.out.println(response.toString());
}
// HTTP POST请求
private void sendPost() throws Exception {
String url = "https://selfsolve.apple.com/wcResults.do";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//添加请求头
con.setRequestMethod("POST");
con.setRequestProperty("User-Agent", USER_AGENT);
con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
String urlParameters = "sn=C02G8416DRJM&cn=&locale=&caller=&num=12345";
//发送Post请求
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
int responseCode = con.getResponseCode();
System.out.println("\nSending 'POST' request to URL : " + url);
System.out.println("Post parameters : " + urlParameters);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//打印结果
System.out.println(response.toString());
}
}
... ...
package org.shadowsocks.crypto;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Random;
import static java.lang.System.arraycopy;
import static org.shadowsocks.crypto.AESCrypto.CIPHER_AES_256_CFB;
public class AESCryptoTest {
@Test
public void encyptTest() throws Exception {
byte[] testCase = "hello world, this is pink floyd".getBytes(StandardCharsets.UTF_8);
AESCrypto cryptoClient = new AESCrypto(CIPHER_AES_256_CFB, "abc123");
AESCrypto cryptoServer = new AESCrypto(CIPHER_AES_256_CFB, "abc123");
byte[] en = cryptoClient.encrypt(testCase, testCase.length);
byte[] de = cryptoServer.decrypt(en, en.length);
Assert.assertArrayEquals(de, testCase);
for (int i = 0; i < 100; i++) {
testCase = Utils.randomBytes(20);
en = cryptoServer.encrypt(testCase, testCase.length);
de = cryptoClient.decrypt(en, en.length);
Assert.assertArrayEquals(de, testCase);
}
}
}
\ No newline at end of file
... ...
package org.shadowsocks.socks5;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
public class HttpRequestTest {
public static void main(String[] args) throws Exception {
final String user = "t";
final String password = "test";
Proxy proxyTest = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10000));
// java.net.Authenticator.setDefault(new java.net.Authenticator()
// {
// private PasswordAuthentication authentication = new PasswordAuthentication(user, password.toCharArray());
//
// @Override
// protected PasswordAuthentication getPasswordAuthentication()
// {
// return authentication;
// }
// });
OkHttpClient client = new OkHttpClient.Builder().proxy(proxyTest).build();
Request request = new Request.Builder().url("http://www.baidu.com").build();
Response response = client.newCall(request).execute();
System.out.println(response.code());
System.out.println(response.body());
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
}
public void socks5PortTest() throws Exception{
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10000));
HttpGet get = new HttpGet("http://www.baidu.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient httpClient = HttpClients.createDefault();
httpClient.execute(get);
}
}
... ...
package org.shadowsocks.socks5;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.shadowsocks.handler.server.StringHandler;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class PortListenTest {
public static void main(String args[]) {
int port = 443;
ServerSocket server_socket;
BufferedReader input;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception e) {
System.out.println("port = 443 (default)");
port = 443;
}
try {
server_socket = new ServerSocket(port);
System.out.println("Server waiting for client on port " +
server_socket.getLocalPort());
// server infinite loop
while(true) {
Socket socket = server_socket.accept();
System.out.println("New connection accepted " +
socket.getInetAddress() +
":" + socket.getPort());
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// print received data
try {
while(true) {
String message = input.readLine();
if (message==null) break;
System.out.println(message);
}
}
catch (IOException e) {
System.out.println(e);
}
// connection closed by client
try {
socket.close();
System.out.println("Connection closed by client");
}
catch (IOException e) {
System.out.println(e);
}
}
}
catch (IOException e) {
System.out.println(e);
}
}
}
... ...
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/java" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
... ...
#!/usr/bin/env bash
#default value for local port
local_port=1080
#default value for cipher method
method="aes-256-cfb"
#default value for server port
server_port=8388
local_address="127.0.0.1"
time_out=600
#parse the parameters
while test $# -gt 0; do
case "$1" in
-h|--help)
echo "A fast tunnel proxy that helps you bypass firewalls."
echo "You can supply configurations via either config file or command line arguments."
echo "Proxy options:"
echo " -c CONFIG path to config file"
echo " -s SERVER_ADDR server address"
echo " -p SERVER_PORT server port, default: 8388"
echo " -b LOCAL_ADDR local binding address, default: 127.0.0.1"
echo " -l LOCAL_PORT local port, default: 1080"
echo " -k PASSWORD password"
echo " -m METHOD encryption method, default: aes-256-cfb"
echo " Sodium:"
echo " chacha20, chacha20-ietf."
echo " OpenSSL:"
echo " aes-{128|192|256}-cfb,aes-{128|192|256}-ofb,"
echo " -t TIMEOUT timeout in seconds, default: 600"
exit 0
;;
-c)
shift
if test $# -gt 0; then
config=$1
fi
shift
;;
-s)
shift
if test $# -gt 0; then
server=$1
fi
shift
;;
-p)
shift
if test $# -gt 0; then
server_port=$1
fi
shift
;;
-b)
shift
if test $# -gt 0; then
local_address=$1
fi
shift
;;
-l)
shift
if test $# -gt 0; then
local_port=$1
fi
shift
;;
-k)
shift
if test $# -gt 0; then
password=$1
fi
shift
;;
-m)
shift
if test $# -gt 0; then
method=$1
fi
shift
;;
-t)
shift
if test $# -gt 0; then
time_out=$1
fi
shift
;;
*)
break
;;
esac
done
main="LocalMain"
#printing some output to the users
echo "config: $config"
if [ -n "$config" ]
then
echo "Starting shadowsocks ...";
java -jar -jar ./target/shadowsocks-java-1.0-SNAPSHOT.jar ${main} config=${config}
elif [ -z "$server" ]
then
echo "please set a server address"
exit 0
elif [ -z "$password" ]
then
echo "please set a password"
exit 0
else
java -jar ./target/shadowsocks-java-1.0-SNAPSHOT.jar ${main} server=${server} server_port=${server_port}
local_address=${local_address} local_port=${local_port} method=${method} password=${password}
fi
... ...
#!/usr/bin/env bash
#default value for local port
local_port=1080
#default value for cipher method
method="aes-256-cfb"
#default value for server port
server_port=8388
local_address="127.0.0.1"
time_out=600
#parse the parameters
while test $# -gt 0; do
case "$1" in
-h|--help)
echo "A fast tunnel proxy that helps you bypass firewalls."
echo "You can supply configurations via either config file or command line arguments."
echo "Proxy options:"
echo " -c CONFIG path to config file"
echo " -s SERVER_ADDR server address"
echo " -p SERVER_PORT server port, default: 8388"
# echo " -b LOCAL_ADDR local binding address, default: 127.0.0.1"
# echo " -l LOCAL_PORT local port, default: 1080"
echo " -k PASSWORD password"
echo " -m METHOD encryption method, default: aes-256-cfb"
echo " Sodium:"
echo " chacha20, chacha20-ietf."
echo " OpenSSL:"
echo " aes-{128|192|256}-cfb,aes-{128|192|256}-ofb,"
echo " -t TIMEOUT timeout in seconds, default: 600"
exit 0
;;
-c)
shift
if test $# -gt 0; then
config=$1
fi
shift
;;
-s)
shift
if test $# -gt 0; then
server=$1
fi
shift
;;
-p)
shift
if test $# -gt 0; then
server_port=$1
fi
shift
;;
-b)
shift
if test $# -gt 0; then
local_address=$1
fi
shift
;;
-l)
shift
if test $# -gt 0; then
local_port=$1
fi
shift
;;
-k)
shift
if test $# -gt 0; then
password=$1
fi
shift
;;
-m)
shift
if test $# -gt 0; then
method=$1
fi
shift
;;
-t)
shift
if test $# -gt 0; then
time_out=$1
fi
shift
;;
*)
break
;;
esac
done
main="ServerMain"
#printing some output to the users
echo "config: $config"
if [ -n "$config" ]
then
echo "Starting shadowsocks ...";
java -jar ./target/shadowsocks-java-1.0-SNAPSHOT.jar config=${config}
elif [ -z "$server" ]
then
echo "please set a server address"
exit 0
elif [ -z "$password" ]
then
echo "please set a password"
exit 0
else
java -jar ./target/shadowsocks-java-1.0-SNAPSHOT.jar ${main} server=${server} server_port=${server_port}
local_address=${local_address} local_port=${local_port} method=${method} password=${password}
fi
... ...