├── .github
└── workflows
│ └── maven.yml
├── .gitignore
├── LICENSE
├── README.md
├── bin
├── start-local-wc-proxy.bat
├── start-local-wc-proxy.sh
├── start-server-wc-proxy.sh
└── stop-local-wc-proxy.bat
├── conf
├── config-example-client.json
├── config-example-server.json
└── config.json
├── pom.xml
└── src
├── main
├── java
│ └── cn
│ │ └── wowspeeder
│ │ ├── Application.java
│ │ ├── SWLocal.java
│ │ ├── SWServer.java
│ │ ├── config
│ │ ├── Config.java
│ │ └── ConfigLoader.java
│ │ ├── encryption
│ │ ├── Base64Encrypt.java
│ │ ├── CryptAeadBase.java
│ │ ├── CryptFactory.java
│ │ ├── CryptSteamBase.java
│ │ ├── CryptUtil.java
│ │ ├── ICrypt.java
│ │ ├── ShadowSocksKey.java
│ │ └── impl
│ │ │ ├── AesCrypt.java
│ │ │ ├── AesGcmCrypt.java
│ │ │ ├── BlowFishCrypt.java
│ │ │ ├── CamelliaCrypt.java
│ │ │ ├── Chacha20Crypt.java
│ │ │ ├── Rc4Md5Crypt.java
│ │ │ └── SeedCrypt.java
│ │ ├── socks5
│ │ ├── DirectClientHandler.java
│ │ ├── RelayHandler.java
│ │ ├── SocksServerConnectHandler.java
│ │ ├── SocksServerHandler.java
│ │ ├── SocksServerInitializer.java
│ │ └── SocksServerUtils.java
│ │ ├── sw
│ │ ├── SWAddrRequest.java
│ │ ├── SWCommon.java
│ │ ├── SWLocalTcpProxyHandler.java
│ │ ├── SWServerCheckerReceive.java
│ │ ├── SWServerCheckerSend.java
│ │ ├── SWServerTcpProxyHandler.java
│ │ └── SocksCommonUtils.java
│ │ └── websocket
│ │ ├── HttpHandShakeRequestHandler.java
│ │ ├── WebSocketClientHandler.java
│ │ ├── WebSocketIndexPageHandler.java
│ │ ├── WebSocketLocalFrameHandler.java
│ │ ├── WebSocketServerFrameHandler.java
│ │ ├── WebSocketServerIndexPage.java
│ │ └── WebSocketServerInitializer.java
└── resources
│ ├── log4j2.xml
│ └── package.xml
└── test
└── java
├── cn
└── wowspeeder
│ └── sw
│ ├── SSLocalTest.java
│ ├── SSServerTest.java
│ └── SocksCommonUtilsTest.java
└── test
├── Base64EncryptTest.java
├── ByteBufWriteBytes.java
├── GetHostAndPortTest.java
├── PatternTest.java
└── Socks5CommandRequestTest.java
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 1.8
20 | uses: actions/setup-java@v1
21 | with:
22 | java-version: 1.8
23 | - name: Build with Maven
24 | run: mvn -B package --file pom.xml
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /logs/
2 | /.idea/
3 | /target/
4 | .iml
5 | *.log
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2020] [zhining_lu@163.com]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # netty-websocket-proxy
2 | A implementation of Forward-proxy in Java base on netty4 framework.
3 |
4 | # Features
5 |
6 | - [x] TCP support
7 | - [x] CDN support
8 | - [x] SOCKS5 support
9 |
10 |
11 | # Environment
12 | * JRE8
13 |
14 | # Install
15 | 1. download netty-websocket-proxy-x.x.x-bin.tar.gz
16 | 2. tar -xzvf netty-websocket-proxy-x.x.x-bin.tar.gz
17 | 3. run
18 | #### as swserver
19 | ```
20 | java -jar ./bin/netty-websocket-proxy-x.x.x.jar -s -conf="./conf/config-example-server.json"
21 | ```
22 | #### as swclient
23 | ```
24 | java -jar ./bin/netty-websocket-proxy-x.x.x.jar -c -conf="./conf/config-example-client.json"
25 | ```
26 | Note: You can also use the command under bin to start the service. After the service starts, you can use Google Chrome and install the SwitchyOmega plug-in to surf the Internet
27 |
28 | # Build
29 | 1. import as maven project
30 | 2. maven package
31 |
32 | ## TODO
33 | * [ ] performance optimization
34 | * [ ] android client
35 | ## Data flow
36 | 
37 | Note: CDN is optional
38 |
--------------------------------------------------------------------------------
/bin/start-local-wc-proxy.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | taskkill -f -t -im javaw.exe >nul 2>nul
3 | cd ../
4 | echo Client service is starting ...
5 | echo.
6 | start javaw -Xms512m -Xmx512m -jar ./lib/netty-websocket-proxy-1.3.5.jar -c -conf="./conf/config-example-client.json"
7 | echo Client service is started.
8 | echo.&echo The proxy program starts successfully, press any key to exit & pause >nul 2>nul
9 | exit
--------------------------------------------------------------------------------
/bin/start-local-wc-proxy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #start websocket-proxy client
4 | currpath=`pwd`
5 | basename=`basename $currpath`
6 | if [ "$basename" = "bin" ];then
7 | cd ../
8 | fi
9 | homepath=`pwd`
10 | log=$homepath/logs/sw-proxy.log
11 | echo "starting client proxy program, logging to $log"
12 | nohup java -Xms512m -Xmx512m -jar ./lib/netty-websocket-proxy-1.3.5.jar -c -conf="./conf/config-example-client.json" >/dev/null 2>&1 &
13 |
--------------------------------------------------------------------------------
/bin/start-server-wc-proxy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #start websocket-proxy server
4 | currpath=`pwd`
5 | basename=`basename $currpath`
6 | if [ "$basename" = "bin" ];then
7 | cd ../
8 | fi
9 | homepath=`pwd`
10 | log=$homepath/logs/sw-proxy.log
11 | echo "starting server proxy program, logging to $log"
12 | nohup java -Xms512m -Xmx512m -jar ./lib/netty-websocket-proxy-1.3.5.jar -s -conf="./conf/config-example-server.json" >/dev/null 2>&1 &
13 |
--------------------------------------------------------------------------------
/bin/stop-local-wc-proxy.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | taskkill -f -t -im javaw.exe
3 | echo.
4 | echo. & pause
5 | exit
--------------------------------------------------------------------------------
/conf/config-example-client.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": "127.0.0.1",
3 | "port_password": {
4 | "80": "889900"
5 | },
6 | "local_address": "0.0.0.0",
7 | "local_port": 18080
8 | }
--------------------------------------------------------------------------------
/conf/config-example-server.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": "0.0.0.0",
3 | "port_password": {
4 | "80": "889900"
5 | }
6 | }
--------------------------------------------------------------------------------
/conf/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": "0.0.0.0",
3 | "port_password": {
4 | "443": "889900"
5 | },
6 | "local_address": "0.0.0.0",
7 | "local_port":1085
8 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | org.netty-websocket-proxy
6 | netty-websocket-proxy
7 | 1.3.5
8 | jar
9 |
10 | netty-websocket-proxy
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 | 2.3.2
23 |
24 | 1.8
25 | 1.8
26 | UTF-8
27 |
28 |
29 |
30 |
31 | org.apache.maven.plugins
32 | maven-jar-plugin
33 | 2.3.2
34 |
35 |
36 | false
37 |
38 | true
39 |
40 | cn.wowspeeder.Application
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.apache.maven.plugins
48 | maven-assembly-plugin
49 | 2.4
50 |
51 |
52 | src/main/resources/package.xml
53 |
54 |
55 |
56 |
57 | make-assembly
58 | package
59 |
60 | single
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | io.netty
71 | netty-all
72 | 4.1.42.Final
73 |
74 |
75 | org.bouncycastle
76 | bcprov-jdk15on
77 | 1.67
78 |
79 |
80 | com.google.crypto.tink
81 | tink
82 | 1.5.0
83 |
84 |
85 | org.apache.logging.log4j
86 | log4j-core
87 | 2.17.1
88 |
89 |
90 | org.apache.logging.log4j
91 | log4j-slf4j-impl
92 | 2.13.2
93 |
94 |
95 | com.lmax
96 | disruptor
97 | 3.3.4
98 |
99 |
100 |
101 | com.google.code.gson
102 | gson
103 | 2.8.9
104 |
105 |
106 |
107 |
108 | commons-cli
109 | commons-cli
110 | 1.4
111 |
112 |
113 |
114 | junit
115 | junit
116 | 4.13.1
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/Application.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder;
2 |
3 | import io.netty.util.internal.logging.InternalLogger;
4 | import io.netty.util.internal.logging.InternalLoggerFactory;
5 | import org.apache.commons.cli.*;
6 |
7 | public class Application {
8 | private static InternalLogger logger = InternalLoggerFactory.getInstance(Application.class);
9 |
10 | public static void main(String[] args) throws Exception {
11 | Options options = new Options();
12 | Option option = new Option("s", "server", false, "server listen address");
13 | options.addOption(option);
14 |
15 | option = new Option("c", "client", false, "server connect address");
16 | options.addOption(option);
17 |
18 | option = new Option("conf", "config", true, "config file path default:conf/config.json");
19 | options.addOption(option);
20 |
21 | CommandLineParser parser = new DefaultParser();
22 | CommandLine commandLine = parser.parse(options, args);
23 |
24 | String configPath = commandLine.getOptionValue("conf", "conf/config.json");
25 |
26 | logger.info("config path:{}", configPath);
27 | if (commandLine.hasOption("s")) {
28 | SWServer.getInstance().start(configPath);
29 | } else if (commandLine.hasOption("c")) {
30 | SWLocal.getInstance().start(configPath);
31 | } else {
32 | logger.error("not found run type");
33 | }
34 |
35 | logger.info("start success!");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/SWLocal.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder;
2 |
3 | import cn.wowspeeder.config.Config;
4 | import cn.wowspeeder.config.ConfigLoader;
5 | import cn.wowspeeder.socks5.SocksServerHandler;
6 | import cn.wowspeeder.sw.SWCommon;
7 | import cn.wowspeeder.sw.SWLocalTcpProxyHandler;
8 | import io.netty.bootstrap.ServerBootstrap;
9 | import io.netty.channel.*;
10 | import io.netty.channel.nio.NioEventLoopGroup;
11 | import io.netty.channel.socket.nio.NioServerSocketChannel;
12 | import io.netty.channel.socket.nio.NioSocketChannel;
13 | import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler;
14 | import io.netty.handler.timeout.IdleState;
15 | import io.netty.handler.timeout.IdleStateEvent;
16 | import io.netty.handler.timeout.IdleStateHandler;
17 | import io.netty.util.internal.logging.InternalLogger;
18 | import io.netty.util.internal.logging.InternalLoggerFactory;
19 |
20 | import java.util.Map;
21 | import java.util.concurrent.TimeUnit;
22 |
23 | public class SWLocal {
24 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SWLocal.class);
25 |
26 |
27 | private static EventLoopGroup bossGroup = new NioEventLoopGroup();
28 | private static EventLoopGroup workerGroup = new NioEventLoopGroup();
29 |
30 | private static SWLocal SWLocal = new SWLocal();
31 |
32 | public static SWLocal getInstance() {
33 | return SWLocal;
34 | }
35 |
36 | private SWLocal() {
37 |
38 | }
39 |
40 | public void start(String configPath) throws Exception {
41 | final Config config = ConfigLoader.load(configPath);
42 | logger.info("load config !");
43 |
44 | for (Map.Entry portPassword : config.getPortPassword().entrySet()) {
45 | startSingle(config.getLocalAddress(), config.getLocalPort(),
46 | config.getServer(),
47 | portPassword.getKey(),
48 | portPassword.getValue());
49 | }
50 | }
51 |
52 | private void startSingle(String socks5Server, Integer socks5Port, String server, Integer port, String password) throws Exception {
53 | ServerBootstrap tcpBootstrap = new ServerBootstrap();
54 |
55 | //local socks5 server ,tcp
56 | tcpBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
57 | .option(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024)// 接收缓冲区为2M
58 | .childOption(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024)// 接收缓冲区为2M
59 | .childOption(ChannelOption.SO_SNDBUF, 2 * 1024 * 1024)// 发送缓冲区为2M
60 | .childOption(ChannelOption.SO_KEEPALIVE, true)
61 | .childOption(ChannelOption.TCP_NODELAY, false)
62 | .childHandler(new ChannelInitializer() {
63 |
64 | @Override
65 | protected void initChannel(NioSocketChannel ctx) throws Exception {
66 | logger.debug("channel initializer");
67 | ctx.pipeline()
68 | //timeout
69 | .addLast("timeout", new IdleStateHandler(0, 0, SWCommon.TCP_PROXY_IDEL_TIME, TimeUnit.SECONDS) {
70 | @Override
71 | protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) {
72 | ctx.close();
73 | return super.newIdleStateEvent(state, first);
74 | }
75 | });
76 |
77 | //socks5
78 | ctx.pipeline()
79 | // .addLast(new LoggingHandler(LogLevel.INFO))
80 | .addLast(new SocksPortUnificationServerHandler())
81 | .addLast(SocksServerHandler.INSTANCE)
82 | .addLast(new SWLocalTcpProxyHandler(server, port, password));
83 | }
84 | });
85 |
86 | // logger.info("TCP Start At Port " + config.get_localPort());
87 | tcpBootstrap.bind(socks5Server, socks5Port).sync();
88 | logger.info("listen at {}:{}", socks5Server, socks5Port);
89 | }
90 |
91 | public void stop() {
92 | if (bossGroup != null) {
93 | bossGroup.shutdownGracefully();
94 | }
95 | if (workerGroup != null) {
96 | workerGroup.shutdownGracefully();
97 | }
98 | logger.info("Stop Server!");
99 | }
100 |
101 | public static void main(String[] args) throws Exception {
102 | try {
103 | getInstance().start("conf/config-example-client.json");
104 | } catch (Exception e) {
105 | e.printStackTrace();
106 | getInstance().stop();
107 | System.exit(-1);
108 | }
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/SWServer.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder;
2 |
3 | import cn.wowspeeder.config.Config;
4 | import cn.wowspeeder.config.ConfigLoader;
5 | import cn.wowspeeder.sw.SWCommon;
6 | import cn.wowspeeder.sw.SWServerCheckerReceive;
7 | import cn.wowspeeder.sw.SWServerCheckerSend;
8 | import cn.wowspeeder.sw.SWServerTcpProxyHandler;
9 | import cn.wowspeeder.websocket.WebSocketServerInitializer;
10 | import io.netty.channel.*;
11 | import io.netty.channel.socket.nio.NioSocketChannel;
12 | import io.netty.handler.ssl.SslContext;
13 | import io.netty.handler.ssl.SslContextBuilder;
14 | import io.netty.handler.ssl.util.SelfSignedCertificate;
15 | import io.netty.handler.timeout.IdleState;
16 | import io.netty.handler.timeout.IdleStateEvent;
17 | import io.netty.handler.timeout.IdleStateHandler;
18 | import io.netty.util.internal.logging.InternalLogger;
19 | import io.netty.util.internal.logging.InternalLoggerFactory;
20 |
21 | import io.netty.bootstrap.ServerBootstrap;
22 | import io.netty.channel.nio.NioEventLoopGroup;
23 | import io.netty.channel.socket.nio.NioServerSocketChannel;
24 |
25 | import java.util.Map;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | public class SWServer {
29 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SWServer.class);
30 |
31 | private static EventLoopGroup bossGroup = new NioEventLoopGroup();
32 | private static EventLoopGroup workerGroup = new NioEventLoopGroup();
33 |
34 | private static SWServer SWServer = new SWServer();
35 | static final boolean SSL = System.getProperty("ssl") != null;
36 |
37 | public static SWServer getInstance() {
38 | return SWServer;
39 | }
40 |
41 | private SWServer() {
42 |
43 | }
44 |
45 | public void start(String configPath) throws Exception {
46 | final Config config = ConfigLoader.load(configPath);
47 | logger.info("load config !");
48 |
49 | for (Map.Entry portPassword : config.getPortPassword().entrySet()) {
50 | startSingle(config.getServer(), portPassword.getKey(), portPassword.getValue());
51 | }
52 | }
53 |
54 | private void startSingle(String server, Integer port, String password) throws Exception {
55 | // Configure SSL.
56 | final SslContext sslCtx;
57 | if (SSL) {
58 | SelfSignedCertificate ssc = new SelfSignedCertificate();
59 | sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
60 | } else {
61 | sslCtx = null;
62 | }
63 |
64 | ServerBootstrap tcpBootstrap = new ServerBootstrap();
65 | tcpBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
66 | .option(ChannelOption.SO_BACKLOG, 5120)
67 | .option(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024)// 接收缓冲区为2M
68 | .childOption(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024)// 接收缓冲区为2M
69 | .childOption(ChannelOption.SO_SNDBUF, 2 * 1024 * 1024)// 发送缓冲区为2M
70 | .childOption(ChannelOption.SO_KEEPALIVE, true)
71 | .childOption(ChannelOption.TCP_NODELAY, false)
72 | .childOption(ChannelOption.SO_LINGER, 1) //关闭时等待1s发送关闭
73 | .childHandler(new ChannelInitializer() {
74 | @Override
75 | protected void initChannel(NioSocketChannel ctx) throws Exception {
76 | // ctx.pipeline().addLast(new SSTcpHandler(config));
77 | logger.debug("channel initializer");
78 |
79 | ctx.attr(SWCommon.PASSWORD).set(password);
80 |
81 | ctx.pipeline()
82 | //timeout
83 | .addLast("timeout", new IdleStateHandler(0, 0, SWCommon.TCP_PROXY_IDEL_TIME, TimeUnit.SECONDS) {
84 | @Override
85 | protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) {
86 | ctx.close();
87 | return super.newIdleStateEvent(state, first);
88 | }
89 | });
90 |
91 | //ss
92 | ctx.pipeline()
93 | // .addLast(new LoggingHandler(LogLevel.INFO))
94 | //ss-in
95 | .addLast("ssCheckerReceive", new SWServerCheckerReceive())
96 | //ss-out
97 | .addLast("ssCheckerSend", new SWServerCheckerSend())
98 | //ss-websocket
99 | .addLast(new WebSocketServerInitializer(sslCtx))
100 | //ss-cypt
101 | // .addLast("ssCipherCodec", new SSCipherCodec())
102 | //ss-proxy
103 | .addLast("ssTcpProxy", new SWServerTcpProxyHandler());
104 |
105 | }
106 | });
107 |
108 | // logger.info("TCP Start At Port " + config.get_localPort());
109 | tcpBootstrap.bind(server, port).sync();
110 | logger.info("listen at {}:{}", server, port);
111 | }
112 |
113 | public void stop() {
114 | if (bossGroup != null) {
115 | bossGroup.shutdownGracefully();
116 | }
117 | if (workerGroup != null) {
118 | workerGroup.shutdownGracefully();
119 | }
120 | logger.info("Stop Server!");
121 | }
122 |
123 |
124 | public static void main(String[] args) throws InterruptedException {
125 | try {
126 | getInstance().start("conf/config-example-server.json");
127 | } catch (Exception e) {
128 | e.printStackTrace();
129 | getInstance().stop();
130 | System.exit(-1);
131 | }
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/config/Config.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.config;
2 |
3 |
4 | import com.google.gson.annotations.SerializedName;
5 |
6 | import java.util.Map;
7 |
8 | public class Config {
9 |
10 | @SerializedName("server")
11 | private String server;
12 |
13 | @SerializedName("port_password")
14 | private Map portPassword;
15 |
16 | @SerializedName("local_address")
17 | private String localAddress;
18 |
19 | @SerializedName("local_port")
20 | private Integer localPort;
21 |
22 | public String getServer() {
23 | return server;
24 | }
25 |
26 | public void setServer(String server) {
27 | this.server = server;
28 | }
29 |
30 | public Map getPortPassword() {
31 | return portPassword;
32 | }
33 |
34 | public void setPortPassword(Map portPassword) {
35 | this.portPassword = portPassword;
36 | }
37 |
38 | public String getLocalAddress() {
39 | return localAddress;
40 | }
41 |
42 | public void setLocalAddress(String localAddress) {
43 | this.localAddress = localAddress;
44 | }
45 |
46 | public Integer getLocalPort() {
47 | return localPort;
48 | }
49 |
50 | public void setLocalPort(Integer localPort) {
51 | this.localPort = localPort;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/config/ConfigLoader.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.config;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import com.google.gson.Gson;
12 | import com.google.gson.stream.JsonReader;
13 |
14 | /**
15 | * 加载Config配置
16 | */
17 | public class ConfigLoader {
18 |
19 | private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
20 |
21 | public static Config load(String file) throws Exception {
22 | InputStream in = null;
23 | try {
24 | in = new FileInputStream(file);
25 | JsonReader reader;
26 | reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
27 | Config config = new Gson().fromJson(reader, Config.class);
28 | reader.close();
29 | return config;
30 | } finally {
31 | if (in != null) {
32 | try {
33 | in.close();
34 | } catch (IOException ignored) {
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/Base64Encrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import sun.misc.BASE64Decoder;
4 | import sun.misc.BASE64Encoder;
5 |
6 | import javax.crypto.Cipher;
7 | import javax.crypto.KeyGenerator;
8 | import java.security.Key;
9 | import java.security.SecureRandom;
10 |
11 | public class Base64Encrypt {
12 | Key key;
13 |
14 |
15 | public static Base64Encrypt base64Encrypt = new Base64Encrypt();
16 |
17 | public static Base64Encrypt getInstance(String key) throws Exception {
18 | base64Encrypt.setKey(key);
19 | return base64Encrypt;
20 | }
21 |
22 | private Base64Encrypt(){}
23 | /**
24 | * 根据参数生成KEY
25 | *
26 | * @param strKey
27 | */
28 | private void setKey(String strKey) throws Exception {
29 | try {
30 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
31 | secureRandom.setSeed(strKey.getBytes());
32 | KeyGenerator generator = KeyGenerator.getInstance("DES");
33 | generator.init(secureRandom);
34 | this.key = generator.generateKey();
35 | generator = null;
36 | } catch (Exception e) {
37 | throw e;
38 | }
39 | }
40 |
41 | /**
42 | * 加密String明文输入,String密文输出
43 | *
44 | * @param strMing
45 | * @return
46 | */
47 | public String getEncString(String strMing) throws Exception {
48 | byte[] byteMi = null;
49 | byte[] byteMing = null;
50 | String strMi = "";
51 | BASE64Encoder base64en = new BASE64Encoder();
52 | try {
53 | byteMing = strMing.getBytes("UTF-8");
54 | byteMi = this.getEncCode(byteMing);
55 | strMi = base64en.encode(byteMi);
56 | } catch (Exception e) {
57 | throw e;
58 | } finally {
59 | base64en = null;
60 | byteMing = null;
61 | byteMi = null;
62 | }
63 | return strMi;
64 | }
65 |
66 | /**
67 | * 解密 以String密文输入,String明文输出
68 | *
69 | * @param strMi
70 | * @return
71 | */
72 | public String getDesString(String strMi) throws Exception {
73 | BASE64Decoder base64De = new BASE64Decoder();
74 | byte[] byteMing = null;
75 | byte[] byteMi = null;
76 | String strMing = "";
77 | try {
78 | byteMi = base64De.decodeBuffer(strMi);
79 | byteMing = this.getDesCode(byteMi);
80 | strMing = new String(byteMing, "UTF-8");
81 | } catch (Exception e) {
82 | throw e;
83 | } finally {
84 | base64De = null;
85 | byteMing = null;
86 | byteMi = null;
87 | }
88 | return strMing;
89 | }
90 |
91 | /**
92 | * 加密以byte[]明文输入,byte[]密文输出
93 | *
94 | * @param byteS
95 | * @return
96 | * @throws Exception
97 | */
98 | private byte[] getEncCode(byte[] byteS) throws Exception {
99 | byte[] byteFina = null;
100 | Cipher cipher;
101 | try {
102 | cipher = Cipher.getInstance("DES");
103 | cipher.init(Cipher.ENCRYPT_MODE, key);
104 | byteFina = cipher.doFinal(byteS);
105 | } catch (Exception e) {
106 | throw e;
107 | } finally {
108 | cipher = null;
109 | }
110 | return byteFina;
111 | }
112 |
113 | /**
114 | * 解密以byte[]密文输入,以byte[]明文输出
115 | *
116 | * @param byteD
117 | * @return
118 | * @throws Exception
119 | */
120 | private byte[] getDesCode(byte[] byteD) throws Exception {
121 | Cipher cipher;
122 | byte[] byteFina = null;
123 | try {
124 | cipher = Cipher.getInstance("DES");
125 | cipher.init(Cipher.DECRYPT_MODE, key);
126 | byteFina = cipher.doFinal(byteD);
127 | } catch (Exception e) {
128 | throw e;
129 | } finally {
130 | cipher = null;
131 | }
132 | return byteFina;
133 | }
134 |
135 | public static byte[] hex2byte(String strhex) {
136 | System.out.println("strhex:"+strhex);
137 | if (strhex == null) {
138 | return null;
139 | }
140 | int l = strhex.length();
141 | byte[] b = new byte[l / 2];
142 | for (int i = 0; i != l / 2; i++) {
143 | b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
144 | }
145 |
146 | return b;
147 | }
148 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/CryptAeadBase.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import io.netty.util.internal.logging.InternalLogger;
4 | import io.netty.util.internal.logging.InternalLoggerFactory;
5 | import org.bouncycastle.crypto.CipherParameters;
6 | import org.bouncycastle.crypto.digests.SHA1Digest;
7 | import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
8 | import org.bouncycastle.crypto.modes.AEADBlockCipher;
9 | import org.bouncycastle.crypto.params.AEADParameters;
10 | import org.bouncycastle.crypto.params.HKDFParameters;
11 | import org.bouncycastle.crypto.params.KeyParameter;
12 |
13 |
14 | import java.io.ByteArrayOutputStream;
15 | import java.nio.ByteBuffer;
16 | import java.security.GeneralSecurityException;
17 | import java.security.SecureRandom;
18 | import java.util.Arrays;
19 | import java.util.concurrent.locks.Lock;
20 | import java.util.concurrent.locks.ReentrantLock;
21 |
22 |
23 | //TODO unfinished
24 | public abstract class CryptAeadBase implements ICrypt {
25 | private static InternalLogger logger = InternalLoggerFactory.getInstance(CryptAeadBase.class);
26 |
27 | protected static int PAYLOAD_SIZE_MASK = 0x3FFF;
28 |
29 | private static byte[] info = "ss-subkey".getBytes();
30 |
31 | private static byte[] ZERO_NONCE = new byte[getNonceLength()];
32 |
33 |
34 | protected final String _name;
35 | protected final ShadowSocksKey _ssKey;
36 | protected final int _keyLength;
37 | private boolean isForUdp = false;
38 | protected boolean _encryptSaltSet;
39 | protected boolean _decryptSaltSet;
40 | protected final Lock encLock = new ReentrantLock();
41 | protected final Lock decLock = new ReentrantLock();
42 | protected AEADBlockCipher encCipher;
43 | protected AEADBlockCipher decCipher;
44 | private byte[] encSubkey;
45 | private byte[] decSubkey;
46 | protected byte[] encNonce;
47 | protected byte[] decNonce;
48 |
49 | protected byte[] encBuffer = new byte[2 + getTagLength() + PAYLOAD_SIZE_MASK + getTagLength()];
50 | protected byte[] decBuffer = new byte[PAYLOAD_SIZE_MASK + getTagLength()];
51 |
52 | /**
53 | * last chunk payload len already read size
54 | */
55 | protected int payloadLenRead = 0;
56 |
57 | /**
58 | * last chunk payload already read size
59 | */
60 | protected int payloadRead = 0;
61 |
62 | public CryptAeadBase(String name, String password) {
63 | _name = name.toLowerCase();
64 | _keyLength = getKeyLength();
65 | _ssKey = new ShadowSocksKey(password, _keyLength);
66 | }
67 |
68 | @Override
69 | public void isForUdp(boolean isForUdp) {
70 | this.isForUdp = isForUdp;
71 | if (!isForUdp) {
72 | if (encNonce == null && decNonce == null) {
73 | encNonce = new byte[getNonceLength()];
74 | decNonce = new byte[getNonceLength()];
75 | }
76 | }
77 | }
78 |
79 | private byte[] genSubkey(byte[] salt) {
80 | HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA1Digest());
81 | hkdf.init(new HKDFParameters(_ssKey.getEncoded(), salt, info));
82 | byte[] okm = new byte[getKeyLength()];
83 | hkdf.generateBytes(okm, 0, getKeyLength());
84 | return okm;
85 | }
86 |
87 | protected static void increment(byte[] nonce) {
88 | for (int i = 0; i < nonce.length; i++) {
89 | ++nonce[i];
90 | if (nonce[i] != 0) {
91 | break;
92 | }
93 | }
94 | }
95 |
96 |
97 | protected CipherParameters getCipherParameters(boolean forEncryption) {
98 | // logger.debug("getCipherParameters subkey:{}",Arrays.toString(forEncryption ? encSubkey : decSubkey));
99 | byte[] nonce;
100 | if (!isForUdp) {
101 | nonce = forEncryption ? Arrays.copyOf(encNonce, getNonceLength()) : Arrays.copyOf(decNonce, getNonceLength());
102 | } else {
103 | nonce = ZERO_NONCE;
104 | }
105 | return new AEADParameters(
106 | new KeyParameter(forEncryption ? encSubkey : decSubkey),
107 | getTagLength() * 8,
108 | nonce
109 | );
110 | }
111 |
112 | @Override
113 | public void encrypt(byte[] data, ByteArrayOutputStream stream) throws Exception {
114 | synchronized (encLock) {
115 | stream.reset();
116 | if (!_encryptSaltSet || isForUdp) {
117 | byte[] salt = randomBytes(getSaltLength());
118 | stream.write(salt);
119 | encSubkey = genSubkey(salt);
120 | encCipher = getCipher(true);
121 | _encryptSaltSet = true;
122 | }
123 | if (!isForUdp) {
124 | _tcpEncrypt(data, stream);
125 | } else {
126 | _udpEncrypt(data, stream);
127 | }
128 | }
129 | }
130 |
131 | @Override
132 | public void encrypt(byte[] data, int length, ByteArrayOutputStream stream) throws Exception {
133 | // logger.debug("{} encrypt {}", this.hashCode(),new String(data, Charset.forName("GBK")));//
134 | byte[] d = Arrays.copyOfRange(data, 0, length);
135 | encrypt(d, stream);
136 | }
137 |
138 | @Override
139 | public void decrypt(byte[] data, ByteArrayOutputStream stream) throws Exception {
140 | byte[] temp;
141 | synchronized (decLock) {
142 | stream.reset();
143 | ByteBuffer buffer = ByteBuffer.wrap(data);
144 | if (decCipher == null || isForUdp) {
145 | _decryptSaltSet = true;
146 | byte[] salt = new byte[getSaltLength()];
147 | buffer.get(salt);
148 | decSubkey = genSubkey(salt);
149 | decCipher = getCipher(false);
150 | temp = new byte[buffer.remaining()];
151 | buffer.get(temp);
152 | } else {
153 | temp = data;
154 | }
155 | if (!isForUdp) {
156 | _tcpDecrypt(temp, stream);
157 | } else {
158 | _udpDecrypt(temp, stream);
159 | }
160 | }
161 | }
162 |
163 | @Override
164 | public void decrypt(byte[] data, int length, ByteArrayOutputStream stream) throws Exception {
165 | // logger.debug("{} decrypt {}", this.hashCode(),Arrays.toString(data));
166 | byte[] d = Arrays.copyOfRange(data, 0, length);
167 | decrypt(d, stream);
168 | }
169 |
170 | private static byte[] randomBytes(int size) {
171 | byte[] bytes = new byte[size];
172 | new SecureRandom().nextBytes(bytes);
173 | return bytes;
174 | }
175 |
176 | private static int getNonceLength() {
177 | return 12;
178 | }
179 |
180 | protected static int getTagLength() {
181 | return 16;
182 | }
183 |
184 | protected abstract AEADBlockCipher getCipher(boolean isEncrypted)
185 | throws GeneralSecurityException;
186 |
187 | protected abstract void _tcpEncrypt(byte[] data, ByteArrayOutputStream stream) throws Exception;
188 |
189 | protected abstract void _tcpDecrypt(byte[] data, ByteArrayOutputStream stream) throws Exception;
190 |
191 | protected abstract void _udpEncrypt(byte[] data, ByteArrayOutputStream stream) throws Exception;
192 |
193 | protected abstract void _udpDecrypt(byte[] data, ByteArrayOutputStream stream) throws Exception;
194 |
195 | protected abstract int getKeyLength();
196 |
197 | protected abstract int getSaltLength();
198 |
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/CryptFactory.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import cn.wowspeeder.encryption.impl.*;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | public class CryptFactory {
12 |
13 | private static Logger logger = LoggerFactory.getLogger(CryptFactory.class);
14 |
15 | private static Map crypts = new HashMap();
16 |
17 | static {
18 | crypts.putAll(AesCrypt.getCiphers());
19 | crypts.putAll(CamelliaCrypt.getCiphers());
20 | crypts.putAll(BlowFishCrypt.getCiphers());
21 | crypts.putAll(SeedCrypt.getCiphers());
22 | crypts.putAll(Rc4Md5Crypt.getCiphers());
23 | crypts.putAll(Chacha20Crypt.getCiphers());
24 | crypts.putAll(AesGcmCrypt.getCiphers());
25 | }
26 |
27 | public static ICrypt get(String name, String password, boolean forUdp) {
28 | String className = crypts.get(name);
29 | if (className == null) {
30 | return null;
31 | }
32 |
33 | try {
34 | Class> clazz = Class.forName(className);
35 | Constructor> constructor = clazz.getConstructor(String.class,
36 | String.class);
37 | ICrypt crypt = (ICrypt) constructor.newInstance(name, password);
38 | crypt.isForUdp(forUdp);
39 | return crypt;
40 | } catch (Exception e) {
41 | logger.error("get crypt error", e);
42 | }
43 |
44 | return null;
45 | }
46 |
47 | public static ICrypt get(String name, String password) {
48 | return get(name, password, false);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/CryptSteamBase.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.security.InvalidAlgorithmParameterException;
6 | import java.security.SecureRandom;
7 | import java.util.Arrays;
8 | import java.util.concurrent.locks.Lock;
9 | import java.util.concurrent.locks.ReentrantLock;
10 |
11 | import javax.crypto.SecretKey;
12 |
13 | import io.netty.util.internal.logging.InternalLogger;
14 | import io.netty.util.internal.logging.InternalLoggerFactory;
15 | import org.bouncycastle.crypto.CipherParameters;
16 | import org.bouncycastle.crypto.StreamCipher;
17 | import org.bouncycastle.crypto.params.KeyParameter;
18 | import org.bouncycastle.crypto.params.ParametersWithIV;
19 |
20 | public abstract class CryptSteamBase implements ICrypt {
21 |
22 | protected final String _name;
23 | protected final SecretKey _key;
24 | protected final ShadowSocksKey _ssKey;
25 | protected final int _ivLength;
26 | protected final int _keyLength;
27 | protected boolean isForUdp;
28 | protected boolean _encryptIVSet;
29 | protected boolean _decryptIVSet;
30 | protected byte[] _encryptIV;
31 | protected byte[] _decryptIV;
32 | protected final Lock encLock = new ReentrantLock();
33 | protected final Lock decLock = new ReentrantLock();
34 | protected StreamCipher encCipher;
35 | protected StreamCipher decCipher;
36 | private static InternalLogger logger = InternalLoggerFactory.getInstance(CryptSteamBase.class);
37 |
38 | public CryptSteamBase(String name, String password) {
39 | _name = name.toLowerCase();
40 | _ivLength = getIVLength();
41 | _keyLength = getKeyLength();
42 | _ssKey = new ShadowSocksKey(password, _keyLength);
43 | _key = getKey();
44 | }
45 |
46 | @Override
47 | public void isForUdp(boolean isForUdp) {
48 | this.isForUdp = isForUdp;
49 | }
50 |
51 | protected void setIV(byte[] iv, boolean isEncrypt) {
52 | if (_ivLength == 0) {
53 | return;
54 | }
55 | CipherParameters cipherParameters = null;
56 |
57 | if (isEncrypt) {
58 | cipherParameters = getCipherParameters(iv);
59 | try {
60 | encCipher = getCipher(isEncrypt);
61 | } catch (InvalidAlgorithmParameterException e) {
62 | logger.info(e.toString());
63 | }
64 | encCipher.init(isEncrypt, cipherParameters);
65 | } else {
66 | _decryptIV = Arrays.copyOfRange(iv,0,_ivLength);
67 | cipherParameters = getCipherParameters(iv);
68 | try {
69 | decCipher = getCipher(isEncrypt);
70 | } catch (InvalidAlgorithmParameterException e) {
71 | logger.info(e.toString());
72 | }
73 | decCipher.init(isEncrypt, cipherParameters);
74 | }
75 | }
76 |
77 | protected CipherParameters getCipherParameters(byte[] iv){
78 | _decryptIV = Arrays.copyOfRange(iv,0,_ivLength);
79 | return new ParametersWithIV(new KeyParameter(_key.getEncoded()), _decryptIV);
80 | }
81 |
82 | @Override
83 | public void encrypt(byte[] data, ByteArrayOutputStream stream) {
84 | synchronized (encLock) {
85 | stream.reset();
86 | if (!_encryptIVSet || isForUdp) {
87 | _encryptIVSet = true;
88 | byte[] iv = randomBytes(_ivLength);
89 | setIV(iv, true);
90 | try {
91 | stream.write(iv);
92 | } catch (IOException e) {
93 | logger.info(e.toString());
94 | }
95 |
96 | }
97 |
98 | _encrypt(data, stream);
99 | }
100 | }
101 |
102 | @Override
103 | public void encrypt(byte[] data, int length, ByteArrayOutputStream stream) {
104 | byte[] d = Arrays.copyOfRange(data,0,length);
105 | encrypt(d, stream);
106 | }
107 |
108 | @Override
109 | public void decrypt(byte[] data, ByteArrayOutputStream stream) {
110 | byte[] temp;
111 | synchronized (decLock) {
112 | stream.reset();
113 | if (!_decryptIVSet || isForUdp) {
114 | _decryptIVSet = true;
115 | setIV(data, false);
116 | temp = Arrays.copyOfRange(data,_ivLength,data.length);
117 | } else {
118 | temp = data;
119 | }
120 |
121 | _decrypt(temp, stream);
122 | }
123 | }
124 |
125 | @Override
126 | public void decrypt(byte[] data, int length, ByteArrayOutputStream stream) {
127 | byte[] d = Arrays.copyOfRange(data,0,length);
128 | decrypt(d, stream);
129 | }
130 |
131 | private byte[] randomBytes(int size) {
132 | byte[] bytes = new byte[size];
133 | new SecureRandom().nextBytes(bytes);
134 | return bytes;
135 | }
136 |
137 | protected abstract StreamCipher getCipher(boolean isEncrypted)
138 | throws InvalidAlgorithmParameterException;
139 |
140 | protected abstract SecretKey getKey();
141 |
142 | protected abstract void _encrypt(byte[] data, ByteArrayOutputStream stream);
143 |
144 | protected abstract void _decrypt(byte[] data, ByteArrayOutputStream stream);
145 |
146 | protected abstract int getIVLength();
147 |
148 | protected abstract int getKeyLength();
149 | }
150 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/CryptUtil.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 |
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import io.netty.buffer.ByteBuf;
10 |
11 | public class CryptUtil {
12 |
13 | private static Logger logger = LoggerFactory.getLogger(CryptUtil.class);
14 |
15 | public static byte[] encrypt(ICrypt crypt, Object msg) throws Exception {
16 | byte[] data = null;
17 | ByteArrayOutputStream _remoteOutStream = null;
18 | try {
19 | _remoteOutStream = new ByteArrayOutputStream(64 * 1024);
20 | ByteBuf bytebuff = (ByteBuf) msg;
21 | int len = bytebuff.readableBytes();
22 | byte[] arr = new byte[len];
23 | bytebuff.getBytes(0, arr);
24 | crypt.encrypt(arr, _remoteOutStream);
25 | data = _remoteOutStream.toByteArray();
26 | } finally {
27 | if (_remoteOutStream != null) {
28 | try {
29 | _remoteOutStream.close();
30 | } catch (IOException e) {
31 | }
32 | }
33 | }
34 | return data;
35 | }
36 |
37 | public static byte[] decrypt(ICrypt crypt, Object msg) throws Exception {
38 | byte[] data = null;
39 | ByteArrayOutputStream _localOutStream = null;
40 | try {
41 | _localOutStream = new ByteArrayOutputStream(64 * 1024);
42 | ByteBuf bytebuff = (ByteBuf) msg;
43 | int len = bytebuff.readableBytes();
44 | byte[] arr = new byte[len];
45 | bytebuff.getBytes(0, arr);
46 | crypt.decrypt(arr, _localOutStream);
47 | data = _localOutStream.toByteArray();
48 | } finally {
49 | if (_localOutStream != null) {
50 | try {
51 | _localOutStream.close();
52 | } catch (IOException e) {
53 | }
54 | }
55 | }
56 | return data;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/ICrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import org.bouncycastle.crypto.InvalidCipherTextException;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.security.GeneralSecurityException;
8 | import java.security.InvalidAlgorithmParameterException;
9 |
10 | /**
11 | * crypt 加密
12 | *
13 | * @author zhaohui
14 | */
15 | public interface ICrypt {
16 |
17 | void isForUdp(boolean isForUdp);
18 |
19 | void encrypt(byte[] data, ByteArrayOutputStream stream) throws Exception;
20 |
21 | void encrypt(byte[] data, int length, ByteArrayOutputStream stream) throws Exception;
22 |
23 | void decrypt(byte[] data, ByteArrayOutputStream stream) throws Exception;
24 |
25 | void decrypt(byte[] data, int length, ByteArrayOutputStream stream) throws Exception;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/ShadowSocksKey.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.security.MessageDigest;
5 |
6 | import javax.crypto.SecretKey;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | /**
12 | * Shadowsocks key generator
13 | */
14 | public class ShadowSocksKey implements SecretKey {
15 |
16 | private static final long serialVersionUID = 1L;
17 | private static Logger logger = LoggerFactory.getLogger(ShadowSocksKey.class);
18 | private final static int KEY_LENGTH = 32;
19 | private byte[] _key;
20 | private int _length;
21 |
22 | public ShadowSocksKey(String password) {
23 | _length = KEY_LENGTH;
24 | _key = init(password);
25 | }
26 |
27 | public ShadowSocksKey(String password, int length) {
28 | _length = length;
29 | _key = init(password);
30 | }
31 |
32 | private byte[] init(String password) {
33 | MessageDigest md = null;
34 | byte[] keys = new byte[KEY_LENGTH];
35 | byte[] temp = null;
36 | byte[] hash = null;
37 | byte[] passwordBytes = null;
38 | int i = 0;
39 |
40 | try {
41 | md = MessageDigest.getInstance("MD5");
42 | passwordBytes = password.getBytes("UTF-8");
43 | } catch (UnsupportedEncodingException e) {
44 | logger.error("ShadowSocksKey: Unsupported string encoding", e);
45 | } catch (Exception e) {
46 | logger.error("init error", e);
47 | return null;
48 | }
49 |
50 | while (i < keys.length) {
51 | if (i == 0) {
52 | hash = md.digest(passwordBytes);
53 | temp = new byte[passwordBytes.length + hash.length];
54 | } else {
55 | System.arraycopy(hash, 0, temp, 0, hash.length);
56 | System.arraycopy(passwordBytes, 0, temp, hash.length, passwordBytes.length);
57 | hash = md.digest(temp);
58 | }
59 | System.arraycopy(hash, 0, keys, i, hash.length);
60 | i += hash.length;
61 | }
62 |
63 | if (_length != KEY_LENGTH) {
64 | byte[] keysl = new byte[_length];
65 | System.arraycopy(keys, 0, keysl, 0, _length);
66 | return keysl;
67 | }
68 | return keys;
69 | }
70 |
71 | @Override
72 | public String getAlgorithm() {
73 | return "shadowsocks";
74 | }
75 |
76 | @Override
77 | public String getFormat() {
78 | return "RAW";
79 | }
80 |
81 | @Override
82 | public byte[] getEncoded() {
83 | return _key;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/AesCrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.security.InvalidAlgorithmParameterException;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import javax.crypto.SecretKey;
9 | import javax.crypto.spec.SecretKeySpec;
10 |
11 | import org.bouncycastle.crypto.StreamBlockCipher;
12 | import org.bouncycastle.crypto.engines.AESEngine;
13 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
14 | import org.bouncycastle.crypto.modes.OFBBlockCipher;
15 | import cn.wowspeeder.encryption.CryptSteamBase;
16 |
17 | /**
18 | * AES 实现类
19 | *
20 | * @author zhaohui
21 | *
22 | */
23 | public class AesCrypt extends CryptSteamBase {
24 |
25 | public final static String CIPHER_AES_128_CFB = "aes-128-cfb";
26 | public final static String CIPHER_AES_192_CFB = "aes-192-cfb";
27 | public final static String CIPHER_AES_256_CFB = "aes-256-cfb";
28 | public final static String CIPHER_AES_128_OFB = "aes-128-ofb";
29 | public final static String CIPHER_AES_192_OFB = "aes-192-ofb";
30 | public final static String CIPHER_AES_256_OFB = "aes-256-ofb";
31 |
32 | public static Map getCiphers() {
33 | Map ciphers = new HashMap<>();
34 | ciphers.put(CIPHER_AES_128_CFB, AesCrypt.class.getName());
35 | ciphers.put(CIPHER_AES_192_CFB, AesCrypt.class.getName());
36 | ciphers.put(CIPHER_AES_256_CFB, AesCrypt.class.getName());
37 | ciphers.put(CIPHER_AES_128_OFB, AesCrypt.class.getName());
38 | ciphers.put(CIPHER_AES_192_OFB, AesCrypt.class.getName());
39 | ciphers.put(CIPHER_AES_256_OFB, AesCrypt.class.getName());
40 |
41 | return ciphers;
42 | }
43 |
44 | public AesCrypt(String name, String password) {
45 | super(name, password);
46 | }
47 |
48 | @Override
49 | public int getKeyLength() {
50 | if (_name.equals(CIPHER_AES_128_CFB)
51 | || _name.equals(CIPHER_AES_128_OFB)) {
52 | return 16;
53 | } else if (_name.equals(CIPHER_AES_192_CFB)
54 | || _name.equals(CIPHER_AES_192_OFB)) {
55 | return 24;
56 | } else if (_name.equals(CIPHER_AES_256_CFB)
57 | || _name.equals(CIPHER_AES_256_OFB)) {
58 | return 32;
59 | }
60 |
61 | return 0;
62 | }
63 |
64 | @Override
65 | protected StreamBlockCipher getCipher(boolean isEncrypted)
66 | throws InvalidAlgorithmParameterException {
67 | AESEngine engine = new AESEngine();
68 | StreamBlockCipher cipher;
69 |
70 | if (_name.equals(CIPHER_AES_128_CFB)) {
71 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
72 | } else if (_name.equals(CIPHER_AES_192_CFB)) {
73 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
74 | } else if (_name.equals(CIPHER_AES_256_CFB)) {
75 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
76 | } else if (_name.equals(CIPHER_AES_128_OFB)) {
77 | cipher = new OFBBlockCipher(engine, getIVLength() * 8);
78 | } else if (_name.equals(CIPHER_AES_192_OFB)) {
79 | cipher = new OFBBlockCipher(engine, getIVLength() * 8);
80 | } else if (_name.equals(CIPHER_AES_256_OFB)) {
81 | cipher = new OFBBlockCipher(engine, getIVLength() * 8);
82 | } else {
83 | throw new InvalidAlgorithmParameterException(_name);
84 | }
85 |
86 | return cipher;
87 | }
88 |
89 | @Override
90 | public int getIVLength() {
91 | return 16;
92 | }
93 |
94 | @Override
95 | protected SecretKey getKey() {
96 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
97 | }
98 |
99 | @Override
100 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
101 | int noBytesProcessed;
102 | byte[] buffer = new byte[data.length];
103 |
104 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer,
105 | 0);
106 | stream.write(buffer, 0, noBytesProcessed);
107 | }
108 |
109 | @Override
110 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
111 | int noBytesProcessed;
112 | byte[] buffer = new byte[data.length];
113 |
114 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer,
115 | 0);
116 | stream.write(buffer, 0, noBytesProcessed);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/AesGcmCrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import cn.wowspeeder.encryption.CryptAeadBase;
4 | import org.bouncycastle.crypto.InvalidCipherTextException;
5 | import org.bouncycastle.crypto.engines.AESEngine;
6 | import org.bouncycastle.crypto.modes.AEADBlockCipher;
7 | import org.bouncycastle.crypto.modes.GCMBlockCipher;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 |
12 | import java.io.ByteArrayOutputStream;
13 | import java.io.IOException;
14 | import java.nio.ByteBuffer;
15 | import java.security.GeneralSecurityException;
16 | import java.security.InvalidAlgorithmParameterException;
17 | import java.util.HashMap;
18 | import java.util.Map;
19 |
20 |
21 | public class AesGcmCrypt extends CryptAeadBase {
22 | private static Logger logger = LoggerFactory.getLogger(AesGcmCrypt.class);
23 |
24 | public final static String CIPHER_AEAD_128_GCM = "aes-128-gcm";
25 | // public final static String CIPHER_AEAD_192_GCM = "aes-192-gcm";
26 | public final static String CIPHER_AEAD_256_GCM = "aes-256-gcm";
27 |
28 | public static Map getCiphers() {
29 | Map ciphers = new HashMap<>();
30 | ciphers.put(CIPHER_AEAD_128_GCM, AesGcmCrypt.class.getName());
31 | // ciphers.put(CIPHER_AEAD_192_GCM, AesGcmCrypt.class.getName());
32 | ciphers.put(CIPHER_AEAD_256_GCM, AesGcmCrypt.class.getName());
33 |
34 | return ciphers;
35 | }
36 |
37 | public AesGcmCrypt(String name, String password) {
38 | super(name, password);
39 | }
40 |
41 | // Nonce Size
42 | @Override
43 | public int getKeyLength() {
44 | switch (_name) {
45 | case CIPHER_AEAD_128_GCM:
46 | return 16;
47 | // case CIPHER_AEAD_192_GCM:
48 | // return 24;
49 | case CIPHER_AEAD_256_GCM:
50 | return 32;
51 | }
52 | return 0;
53 | }
54 |
55 | @Override
56 | protected AEADBlockCipher getCipher(boolean isEncrypted)
57 | throws GeneralSecurityException {
58 | switch (_name) {
59 | case CIPHER_AEAD_128_GCM:
60 | case CIPHER_AEAD_256_GCM:
61 | return new GCMBlockCipher(new AESEngine());
62 | default:
63 | throw new InvalidAlgorithmParameterException(_name);
64 | }
65 | }
66 |
67 | @Override
68 | public int getSaltLength() {
69 | switch (_name) {
70 | case CIPHER_AEAD_128_GCM:
71 | return 16;
72 | // case CIPHER_AEAD_192_GCM:
73 | // return 24;
74 | case CIPHER_AEAD_256_GCM:
75 | return 32;
76 | }
77 | return 0;
78 | }
79 |
80 | /**
81 | * TCP:[encrypted payload length][length tag][encrypted payload][payload tag]
82 | * UDP:[salt][encrypted payload][tag]
83 | * //TODO need return multi chunks
84 | *
85 | * @param data
86 | * @param stream
87 | * @throws GeneralSecurityException
88 | * @throws IOException
89 | */
90 | @Override
91 | protected void _tcpEncrypt(byte[] data, ByteArrayOutputStream stream) throws GeneralSecurityException, IOException, InvalidCipherTextException {
92 | // byte[] buffer = new byte[data.length];
93 | // int noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
94 | // stream.write(buffer, 0, noBytesProcessed);
95 | ByteBuffer buffer = ByteBuffer.wrap(data);
96 | while (buffer.hasRemaining()) {
97 | int nr = Math.min(buffer.remaining(), PAYLOAD_SIZE_MASK);
98 | ByteBuffer.wrap(encBuffer).putShort((short) nr);
99 | encCipher.init(true, getCipherParameters(true));
100 | encCipher.doFinal(
101 | encBuffer,
102 | encCipher.processBytes(encBuffer, 0, 2, encBuffer, 0)
103 | );
104 | stream.write(encBuffer, 0, 2 + getTagLength());
105 | increment(this.encNonce);
106 |
107 | buffer.get(encBuffer, 2 + getTagLength(), nr);
108 |
109 | encCipher.init(true, getCipherParameters(true));
110 | encCipher.doFinal(
111 | encBuffer,
112 | 2 + getTagLength() + encCipher.processBytes(encBuffer, 2 + getTagLength(), nr, encBuffer, 2 + getTagLength())
113 | );
114 | increment(this.encNonce);
115 |
116 | stream.write(encBuffer, 2 + getTagLength(), nr + getTagLength());
117 | }
118 | }
119 |
120 | /**
121 | * @param data
122 | * @param stream
123 | * @throws InvalidCipherTextException
124 | */
125 | @Override
126 | protected void _tcpDecrypt(byte[] data, ByteArrayOutputStream stream) throws InvalidCipherTextException {
127 | // byte[] buffer = new byte[data.length];
128 | // int noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer,
129 | // 0);
130 | // logger.debug("remaining _tcpDecrypt");
131 | // stream.write(buffer, 0, noBytesProcessed);
132 | // logger.debug("ciphertext len:{}", data.length);
133 | ByteBuffer buffer = ByteBuffer.wrap(data);
134 | while (buffer.hasRemaining()) {
135 | logger.debug("id:{} remaining {} payloadLenRead:{} payloadRead:{}", hashCode(), buffer.hasRemaining(), payloadLenRead, payloadRead);
136 | if (payloadRead == 0) {
137 | // [encrypted payload length][length tag]
138 | int wantLen = 2 + getTagLength() - payloadLenRead;
139 | int remaining = buffer.remaining();
140 | if (wantLen <= remaining) {
141 | buffer.get(decBuffer, payloadLenRead, wantLen);
142 | } else {
143 | buffer.get(decBuffer, payloadLenRead, remaining);
144 | payloadLenRead += remaining;
145 | return;
146 | }
147 | decCipher.init(false, getCipherParameters(false));
148 | decCipher.doFinal(
149 | decBuffer,
150 | decCipher.processBytes(decBuffer, 0, 2 + getTagLength(), decBuffer, 0)
151 | );
152 | increment(decNonce);
153 | }
154 |
155 |
156 | // [encrypted payload length][length tag]
157 | int size = ByteBuffer.wrap(decBuffer, 0, 2).getShort();
158 | logger.debug("payload length:{},remaining:{},payloadRead:{}", size, buffer.remaining(), payloadRead);
159 | if (size == 0) {
160 | //TODO exists?
161 | return;
162 | } else {
163 | int wantLen = getTagLength() + size - payloadRead;
164 | int remaining = buffer.remaining();
165 | if (wantLen <= remaining) {
166 | buffer.get(decBuffer, 2 + getTagLength() + payloadRead, wantLen);
167 | } else {
168 | buffer.get(decBuffer, 2 + getTagLength() + payloadRead, remaining);
169 | payloadRead += remaining;
170 | return;
171 | }
172 | }
173 |
174 | decCipher.init(false, getCipherParameters(false));
175 | decCipher.doFinal(
176 | decBuffer,
177 | (2 + getTagLength()) + decCipher.processBytes(decBuffer, 2 + getTagLength(), size + getTagLength(), decBuffer, 2 + getTagLength())
178 | );
179 | increment(decNonce);
180 |
181 | payloadLenRead = 0;
182 | payloadRead = 0;
183 |
184 | stream.write(decBuffer, 2 + getTagLength(), size);
185 | // logger.debug("cipher text decode finish");
186 | }
187 | }
188 |
189 | @Override
190 | protected void _udpEncrypt(byte[] data, ByteArrayOutputStream stream) throws Exception {
191 | ByteBuffer buffer = ByteBuffer.wrap(data);
192 | int remaining = buffer.remaining();
193 | buffer.get(encBuffer, 0, remaining);
194 | encCipher.init(true, getCipherParameters(true));
195 | encCipher.doFinal(
196 | encBuffer,
197 | encCipher.processBytes(encBuffer, 0, remaining, encBuffer, 0)
198 | );
199 | stream.write(encBuffer, 0, remaining + getTagLength());
200 | }
201 |
202 | @Override
203 | protected void _udpDecrypt(byte[] data, ByteArrayOutputStream stream) throws Exception {
204 | ByteBuffer buffer = ByteBuffer.wrap(data);
205 | int remaining = buffer.remaining();
206 | buffer.get(decBuffer, 0, remaining);
207 | decCipher.init(false, getCipherParameters(false));
208 | decCipher.doFinal(
209 | decBuffer,
210 | decCipher.processBytes(decBuffer, 0, remaining, decBuffer, 0)
211 | );
212 | stream.write(decBuffer, 0, remaining - getTagLength());
213 | }
214 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/BlowFishCrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import org.bouncycastle.crypto.StreamBlockCipher;
4 | import org.bouncycastle.crypto.engines.BlowfishEngine;
5 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
6 | import cn.wowspeeder.encryption.CryptSteamBase;
7 |
8 | import javax.crypto.SecretKey;
9 | import javax.crypto.spec.SecretKeySpec;
10 | import java.io.ByteArrayOutputStream;
11 | import java.security.InvalidAlgorithmParameterException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * Blow fish cipher implementation
17 | */
18 | public class BlowFishCrypt extends CryptSteamBase {
19 |
20 | public final static String CIPHER_BLOWFISH_CFB = "bf-cfb";
21 |
22 | public static Map getCiphers() {
23 | Map ciphers = new HashMap<>();
24 | ciphers.put(CIPHER_BLOWFISH_CFB, BlowFishCrypt.class.getName());
25 |
26 | return ciphers;
27 | }
28 |
29 | public BlowFishCrypt(String name, String password) {
30 | super(name, password);
31 | }
32 |
33 | @Override
34 | public int getKeyLength() {
35 | return 16;
36 | }
37 |
38 | @Override
39 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
40 | BlowfishEngine engine = new BlowfishEngine();
41 | StreamBlockCipher cipher;
42 |
43 | if (_name.equals(CIPHER_BLOWFISH_CFB)) {
44 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
45 | }
46 | else {
47 | throw new InvalidAlgorithmParameterException(_name);
48 | }
49 |
50 | return cipher;
51 | }
52 |
53 | @Override
54 | public int getIVLength() {
55 | return 8;
56 | }
57 |
58 | @Override
59 | protected SecretKey getKey() {
60 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
61 | }
62 |
63 | @Override
64 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
65 | int noBytesProcessed;
66 | byte[] buffer = new byte[data.length];
67 |
68 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
69 | stream.write(buffer, 0, noBytesProcessed);
70 | }
71 |
72 | @Override
73 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
74 | int noBytesProcessed;
75 | byte[] buffer = new byte[data.length];
76 |
77 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
78 | stream.write(buffer, 0, noBytesProcessed);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/CamelliaCrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import org.bouncycastle.crypto.StreamBlockCipher;
4 | import org.bouncycastle.crypto.engines.CamelliaEngine;
5 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
6 | import cn.wowspeeder.encryption.CryptSteamBase;
7 |
8 | import javax.crypto.SecretKey;
9 | import javax.crypto.spec.SecretKeySpec;
10 | import java.io.ByteArrayOutputStream;
11 | import java.security.InvalidAlgorithmParameterException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * Camellia cipher implementation
17 | */
18 | public class CamelliaCrypt extends CryptSteamBase {
19 |
20 | public final static String CIPHER_CAMELLIA_128_CFB = "camellia-128-cfb";
21 | public final static String CIPHER_CAMELLIA_192_CFB = "camellia-192-cfb";
22 | public final static String CIPHER_CAMELLIA_256_CFB = "camellia-256-cfb";
23 |
24 | public static Map getCiphers() {
25 | Map ciphers = new HashMap<>();
26 | ciphers.put(CIPHER_CAMELLIA_128_CFB, CamelliaCrypt.class.getName());
27 | ciphers.put(CIPHER_CAMELLIA_192_CFB, CamelliaCrypt.class.getName());
28 | ciphers.put(CIPHER_CAMELLIA_256_CFB, CamelliaCrypt.class.getName());
29 |
30 | return ciphers;
31 | }
32 |
33 | public CamelliaCrypt(String name, String password) {
34 | super(name, password);
35 | }
36 |
37 | @Override
38 | public int getKeyLength() {
39 | if(_name.equals(CIPHER_CAMELLIA_128_CFB)) {
40 | return 16;
41 | }
42 | else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {
43 | return 24;
44 | }
45 | else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {
46 | return 32;
47 | }
48 |
49 | return 0;
50 | }
51 |
52 | @Override
53 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
54 | CamelliaEngine engine = new CamelliaEngine();
55 | StreamBlockCipher cipher;
56 |
57 | if (_name.equals(CIPHER_CAMELLIA_128_CFB)) {
58 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
59 | }
60 | else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {
61 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
62 | }
63 | else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {
64 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
65 | }
66 | else {
67 | throw new InvalidAlgorithmParameterException(_name);
68 | }
69 |
70 | return cipher;
71 | }
72 |
73 | @Override
74 | public int getIVLength() {
75 | return 16;
76 | }
77 |
78 | @Override
79 | protected SecretKey getKey() {
80 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
81 | }
82 |
83 | @Override
84 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
85 | int noBytesProcessed;
86 | byte[] buffer = new byte[data.length];
87 |
88 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
89 | stream.write(buffer, 0, noBytesProcessed);
90 | }
91 |
92 | @Override
93 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
94 | int noBytesProcessed;
95 | byte[] buffer = new byte[data.length];
96 |
97 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
98 | stream.write(buffer, 0, noBytesProcessed);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/Chacha20Crypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import cn.wowspeeder.encryption.CryptSteamBase;
4 | import org.bouncycastle.crypto.StreamCipher;
5 | import org.bouncycastle.crypto.engines.ChaCha7539Engine;
6 | import org.bouncycastle.crypto.engines.ChaChaEngine;
7 |
8 | import javax.crypto.SecretKey;
9 | import javax.crypto.spec.SecretKeySpec;
10 | import java.io.ByteArrayOutputStream;
11 | import java.security.InvalidAlgorithmParameterException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | public class Chacha20Crypt extends CryptSteamBase {
16 | public final static String CIPHER_CHACHA20 = "chacha20";
17 | public final static String CIPHER_CHACHA20_IETF = "chacha20-ietf";
18 |
19 | public static Map getCiphers() {
20 | Map ciphers = new HashMap<>();
21 | ciphers.put(CIPHER_CHACHA20, Chacha20Crypt.class.getName());
22 | ciphers.put(CIPHER_CHACHA20_IETF, Chacha20Crypt.class.getName());
23 | return ciphers;
24 | }
25 |
26 | public Chacha20Crypt(String name, String password) {
27 | super(name, password);
28 | }
29 |
30 | @Override
31 | protected StreamCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
32 | if (_name.equals(CIPHER_CHACHA20)) {
33 | return new ChaChaEngine();
34 | }
35 | else if (_name.equals(CIPHER_CHACHA20_IETF)) {
36 | return new ChaCha7539Engine();
37 | }
38 | return null;
39 | }
40 |
41 | @Override
42 | protected SecretKey getKey() {
43 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
44 |
45 | }
46 |
47 | @Override
48 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
49 | int noBytesProcessed;
50 | byte[] buffer = new byte[data.length];
51 |
52 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
53 | stream.write(buffer, 0, noBytesProcessed);
54 | }
55 |
56 | @Override
57 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
58 | int BytesProcessedNum;
59 | byte[] buffer = new byte[data.length];
60 | BytesProcessedNum = decCipher.processBytes(data, 0, data.length, buffer, 0);
61 | stream.write(buffer, 0, BytesProcessedNum);
62 |
63 | }
64 |
65 | @Override
66 | public int getKeyLength() {
67 | if (_name.equals(CIPHER_CHACHA20) || _name.equals(CIPHER_CHACHA20_IETF)) {
68 | return 32;
69 | }
70 | return 0;
71 | }
72 |
73 | @Override
74 | public int getIVLength() {
75 | if (_name.equals(CIPHER_CHACHA20)) {
76 | return 8;
77 | }
78 | else if (_name.equals(CIPHER_CHACHA20_IETF)) {
79 | return 12;
80 | }
81 | return 0;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/Rc4Md5Crypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import cn.wowspeeder.encryption.CryptSteamBase;
4 | import org.bouncycastle.crypto.CipherParameters;
5 | import org.bouncycastle.crypto.StreamCipher;
6 | import org.bouncycastle.crypto.engines.RC4Engine;
7 | import org.bouncycastle.crypto.params.KeyParameter;
8 |
9 | import javax.crypto.SecretKey;
10 | import javax.crypto.spec.SecretKeySpec;
11 | import java.io.ByteArrayOutputStream;
12 | import java.security.InvalidAlgorithmParameterException;
13 | import java.security.MessageDigest;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 |
17 | public class Rc4Md5Crypt extends CryptSteamBase {
18 | public static String CIPHER_RC4_MD5 = "rc4-md5";
19 |
20 | public static Map getCiphers() {
21 | Map ciphers = new HashMap();
22 | ciphers.put(CIPHER_RC4_MD5, Rc4Md5Crypt.class.getName());
23 | return ciphers;
24 | }
25 |
26 |
27 | public Rc4Md5Crypt(String name, String password) {
28 | super(name, password);
29 | }
30 |
31 | @Override
32 | protected StreamCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
33 | return new RC4Engine();
34 | }
35 |
36 | @Override
37 | protected SecretKey getKey() {
38 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
39 | }
40 |
41 | @Override
42 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
43 | int noBytesProcessed;
44 | byte[] buffer = new byte[data.length];
45 |
46 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
47 | stream.write(buffer, 0, noBytesProcessed);
48 | }
49 |
50 | @Override
51 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
52 | int noBytesProcessed;
53 | byte[] buffer = new byte[data.length];
54 |
55 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
56 | stream.write(buffer, 0, noBytesProcessed);
57 | }
58 |
59 | @Override
60 | protected int getIVLength() {
61 | return 16;
62 | }
63 |
64 | @Override
65 | protected int getKeyLength() {
66 | return 16;
67 | }
68 |
69 | @Override
70 | protected CipherParameters getCipherParameters(byte[] iv) {
71 | byte[] bts = new byte[_keyLength + _ivLength];
72 | System.arraycopy(_key.getEncoded(), 0, bts, 0, _keyLength);
73 | System.arraycopy(iv, 0, bts, _keyLength, _ivLength);
74 | return new KeyParameter(md5Digest(bts));
75 | }
76 |
77 | public static byte[] md5Digest(byte[] input) {
78 | try {
79 | MessageDigest md5 = MessageDigest.getInstance("MD5");
80 | return md5.digest(input);
81 | } catch (Exception e) {
82 | throw new RuntimeException(e);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/encryption/impl/SeedCrypt.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.encryption.impl;
2 |
3 | import org.bouncycastle.crypto.StreamBlockCipher;
4 | import org.bouncycastle.crypto.engines.SEEDEngine;
5 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
6 | import cn.wowspeeder.encryption.CryptSteamBase;
7 |
8 | import javax.crypto.SecretKey;
9 | import javax.crypto.spec.SecretKeySpec;
10 | import java.io.ByteArrayOutputStream;
11 | import java.security.InvalidAlgorithmParameterException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * Seed cipher implementation
17 | */
18 | public class SeedCrypt extends CryptSteamBase {
19 |
20 | public final static String CIPHER_SEED_CFB = "seed-cfb";
21 |
22 | public static Map getCiphers() {
23 | Map ciphers = new HashMap<>();
24 | ciphers.put(CIPHER_SEED_CFB, SeedCrypt.class.getName());
25 |
26 | return ciphers;
27 | }
28 |
29 | public SeedCrypt(String name, String password) {
30 | super(name, password);
31 | }
32 |
33 | @Override
34 | public int getKeyLength() {
35 | return 16;
36 | }
37 |
38 | @Override
39 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
40 | SEEDEngine engine = new SEEDEngine();
41 | StreamBlockCipher cipher;
42 |
43 | if (_name.equals(CIPHER_SEED_CFB)) {
44 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
45 | }
46 | else {
47 | throw new InvalidAlgorithmParameterException(_name);
48 | }
49 |
50 | return cipher;
51 | }
52 |
53 | @Override
54 | public int getIVLength() {
55 | return 16;
56 | }
57 |
58 | @Override
59 | protected SecretKey getKey() {
60 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
61 | }
62 |
63 | @Override
64 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
65 | int noBytesProcessed;
66 | byte[] buffer = new byte[data.length];
67 |
68 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
69 | stream.write(buffer, 0, noBytesProcessed);
70 | }
71 |
72 | @Override
73 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
74 | int noBytesProcessed;
75 | byte[] buffer = new byte[data.length];
76 |
77 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
78 | stream.write(buffer, 0, noBytesProcessed);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/socks5/DirectClientHandler.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.socks5;
2 |
3 |
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.ChannelInboundHandlerAdapter;
7 | import io.netty.util.concurrent.Promise;
8 |
9 | public final class DirectClientHandler extends ChannelInboundHandlerAdapter {
10 |
11 | private final Promise promise;
12 |
13 | public DirectClientHandler(Promise promise) {
14 | this.promise = promise;
15 | }
16 |
17 | @Override
18 | public void channelActive(ChannelHandlerContext ctx) {
19 | ctx.pipeline().remove(this);
20 | promise.setSuccess(ctx.channel());
21 | }
22 |
23 | @Override
24 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
25 | promise.setFailure(throwable);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/socks5/RelayHandler.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.socks5;
2 |
3 |
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.Channel;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.ChannelInboundHandlerAdapter;
8 | import io.netty.util.ReferenceCountUtil;
9 |
10 | public final class RelayHandler extends ChannelInboundHandlerAdapter {
11 |
12 | private final Channel relayChannel;
13 |
14 | public RelayHandler(Channel relayChannel) {
15 | this.relayChannel = relayChannel;
16 | }
17 |
18 | @Override
19 | public void channelActive(ChannelHandlerContext ctx) {
20 | ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
21 | }
22 |
23 | @Override
24 | public void channelRead(ChannelHandlerContext ctx, Object msg) {
25 | if (relayChannel.isActive()) {
26 | relayChannel.writeAndFlush(msg);
27 | } else {
28 | ReferenceCountUtil.release(msg);
29 | }
30 | }
31 |
32 | @Override
33 | public void channelInactive(ChannelHandlerContext ctx) {
34 | if (relayChannel.isActive()) {
35 | SocksServerUtils.closeOnFlush(relayChannel);
36 | }
37 | }
38 |
39 | @Override
40 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
41 | cause.printStackTrace();
42 | ctx.close();
43 | }
44 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/socks5/SocksServerConnectHandler.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.socks5;
2 |
3 |
4 | import io.netty.bootstrap.Bootstrap;
5 | import io.netty.channel.Channel;
6 | import io.netty.channel.ChannelFuture;
7 | import io.netty.channel.ChannelFutureListener;
8 | import io.netty.channel.ChannelHandler;
9 | import io.netty.channel.ChannelHandlerContext;
10 | import io.netty.channel.ChannelOption;
11 | import io.netty.channel.SimpleChannelInboundHandler;
12 | import io.netty.channel.socket.nio.NioSocketChannel;
13 | import io.netty.handler.codec.socksx.SocksMessage;
14 | import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
15 | import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
16 | import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
17 | import io.netty.util.concurrent.Future;
18 | import io.netty.util.concurrent.FutureListener;
19 | import io.netty.util.concurrent.Promise;
20 |
21 | @ChannelHandler.Sharable
22 | public final class SocksServerConnectHandler extends SimpleChannelInboundHandler {
23 |
24 | private final Bootstrap b = new Bootstrap();
25 |
26 | @Override
27 | public void channelRead0(final ChannelHandlerContext ctx, final SocksMessage message) throws Exception {
28 | if (message instanceof Socks5CommandRequest) {
29 | final Socks5CommandRequest request = (Socks5CommandRequest) message;
30 | Promise promise = ctx.executor().newPromise();
31 | promise.addListener(
32 | (FutureListener) future -> {
33 | final Channel outboundChannel = future.getNow();
34 | if (future.isSuccess()) {
35 | ChannelFuture responseFuture =
36 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
37 | Socks5CommandStatus.SUCCESS,
38 | request.dstAddrType(),
39 | request.dstAddr(),
40 | request.dstPort()));
41 |
42 | responseFuture.addListener((ChannelFutureListener) channelFuture -> {
43 | ctx.pipeline().remove(SocksServerConnectHandler.this);
44 | // src <- des
45 | outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel()));
46 | // src -> des
47 | ctx.pipeline().addLast(new RelayHandler(outboundChannel));
48 | });
49 | } else {
50 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
51 | Socks5CommandStatus.FAILURE, request.dstAddrType()));
52 | SocksServerUtils.closeOnFlush(ctx.channel());
53 | }
54 | });
55 |
56 |
57 |
58 | final Channel inboundChannel = ctx.channel();
59 | b.group(inboundChannel.eventLoop())
60 | .channel(NioSocketChannel.class)
61 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
62 | .option(ChannelOption.SO_KEEPALIVE, true)
63 | .handler(new DirectClientHandler(promise));
64 |
65 | b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() {
66 | @Override
67 | public void operationComplete(ChannelFuture future) throws Exception {
68 | if (future.isSuccess()) {
69 | // Connection established use handler provided results
70 | } else {
71 | // Close the connection if the connection attempt has failed.
72 | inboundChannel.writeAndFlush(
73 | new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, request.dstAddrType()));
74 | SocksServerUtils.closeOnFlush(inboundChannel);
75 | }
76 | }
77 | });
78 | } else {
79 | ctx.close();
80 | }
81 | }
82 |
83 | @Override
84 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
85 | SocksServerUtils.closeOnFlush(ctx.channel());
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/socks5/SocksServerHandler.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.socks5;
2 |
3 |
4 | import cn.wowspeeder.sw.SWCommon;
5 | import io.netty.channel.ChannelHandler;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.SimpleChannelInboundHandler;
8 | import io.netty.handler.codec.socksx.SocksMessage;
9 | import io.netty.handler.codec.socksx.v5.*;
10 | import io.netty.util.internal.logging.InternalLogger;
11 | import io.netty.util.internal.logging.InternalLoggerFactory;
12 |
13 | import java.net.*;
14 |
15 | @ChannelHandler.Sharable
16 | public final class SocksServerHandler extends SimpleChannelInboundHandler {
17 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SocksServerHandler.class);
18 |
19 | public static final SocksServerHandler INSTANCE = new SocksServerHandler();
20 |
21 | private SocksServerHandler() {
22 | }
23 |
24 | @Override
25 | public void channelRead0(ChannelHandlerContext ctx, SocksMessage socksRequest) throws Exception {
26 | switch (socksRequest.version()) {
27 | case SOCKS5:
28 | if (socksRequest instanceof Socks5InitialRequest) {
29 | // auth support example
30 | //ctx.pipeline().addFirst(new Socks5PasswordAuthRequestDecoder());
31 | //ctx.write(new DefaultSocks5AuthMethodResponse(Socks5AuthMethod.PASSWORD));
32 |
33 | ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
34 | ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH));
35 | } else if (socksRequest instanceof Socks5CommandRequest) {
36 | Socks5CommandRequest socks5CmdRequest = (Socks5CommandRequest) socksRequest;
37 | if (socks5CmdRequest.type() == Socks5CommandType.CONNECT) {
38 | // ctx.pipeline().addLast(new SocksServerConnectHandler());
39 | ctx.pipeline().remove(this);
40 | //ss-local just res SUCCESS
41 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
42 | Socks5CommandStatus.SUCCESS,
43 | Socks5AddressType.IPv4,
44 | "0.0.0.0",
45 | 0));
46 |
47 | ctx.channel().attr(SWCommon.REMOTE_DES_SOCKS5).set(socks5CmdRequest);
48 |
49 | // ctx.fireChannelRead(socksRequest);
50 |
51 | } else if (socks5CmdRequest.type() == Socks5CommandType.UDP_ASSOCIATE) {
52 | ctx.pipeline().remove(this);
53 |
54 | InetSocketAddress bindAddr = (InetSocketAddress) ctx.channel().localAddress();
55 | InetAddress bindId = bindAddr.getAddress();
56 | Socks5AddressType bindAddrType = Socks5AddressType.IPv4;
57 | if (bindId instanceof Inet4Address) {
58 | bindAddrType = Socks5AddressType.IPv4;
59 | } else if (bindId instanceof Inet6Address) {
60 | bindAddrType = Socks5AddressType.IPv6;
61 | }
62 |
63 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
64 | Socks5CommandStatus.SUCCESS,
65 | bindAddrType,
66 | bindId.getHostAddress(),
67 | bindAddr.getPort()));
68 |
69 | // ctx.fireChannelRead(socksRequest);
70 | } else {
71 | ctx.close();
72 | }
73 |
74 | } else {
75 | ctx.close();
76 | }
77 | break;
78 | case UNKNOWN:
79 | ctx.close();
80 | break;
81 | }
82 | // ReferenceCountUtil.release(socksRequest);
83 |
84 | }
85 |
86 | @Override
87 | public void channelReadComplete(ChannelHandlerContext ctx) {
88 | ctx.flush();
89 | ctx.fireChannelReadComplete();
90 | }
91 |
92 | @Override
93 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
94 | // throwable.printStackTrace();
95 | // SocksServerUtils.closeOnFlush(ctx.channel());
96 | ctx.fireExceptionCaught(throwable);
97 | }
98 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/socks5/SocksServerInitializer.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.socks5;
2 |
3 | import io.netty.channel.ChannelInitializer;
4 | import io.netty.channel.socket.SocketChannel;
5 | import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler;
6 | import io.netty.handler.logging.LogLevel;
7 | import io.netty.handler.logging.LoggingHandler;
8 |
9 | public final class SocksServerInitializer extends ChannelInitializer {
10 | @Override
11 | public void initChannel(SocketChannel ch) throws Exception {
12 | ch.pipeline().addLast(
13 | // new LoggingHandler(LogLevel.INFO),
14 | new SocksPortUnificationServerHandler(),
15 | SocksServerHandler.INSTANCE);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/socks5/SocksServerUtils.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.socks5;
2 |
3 | import io.netty.buffer.Unpooled;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelFutureListener;
6 |
7 | public final class SocksServerUtils {
8 |
9 | /**
10 | * Closes the specified channel after all queued write requests are flushed.
11 | */
12 | public static void closeOnFlush(Channel ch) {
13 | if (ch.isActive()) {
14 | ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
15 | }
16 | }
17 |
18 | private SocksServerUtils() { }
19 | }
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/sw/SWAddrRequest.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.sw;
2 |
3 |
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.handler.codec.socks.SocksAddressType;
6 | import io.netty.util.CharsetUtil;
7 | import io.netty.util.NetUtil;
8 |
9 | import java.net.IDN;
10 |
11 | public final class SWAddrRequest {
12 | private final SocksAddressType addressType;
13 | private final String host;
14 | private final int port;
15 |
16 | public SWAddrRequest(SocksAddressType addressType, String host, int port) {
17 | if (addressType == null) {
18 | throw new NullPointerException("addressType");
19 | } else if (host == null) {
20 | throw new NullPointerException("host");
21 | } else {
22 | switch(addressType) {
23 | case IPv4:
24 | if (!NetUtil.isValidIpV4Address(host)) {
25 | throw new IllegalArgumentException(host + " is not a valid IPv4 address");
26 | }
27 | break;
28 | case DOMAIN:
29 | if (IDN.toASCII(host).length() > 255) {
30 | throw new IllegalArgumentException(host + " IDN: " + IDN.toASCII(host) + " exceeds 255 char limit");
31 | }
32 | break;
33 | case IPv6:
34 | if (!NetUtil.isValidIpV6Address(host)) {
35 | throw new IllegalArgumentException(host + " is not a valid IPv6 address");
36 | }
37 | case UNKNOWN:
38 | }
39 |
40 | if (port > 0 && port < 65536) {
41 | this.addressType = addressType;
42 | this.host = IDN.toASCII(host);
43 | this.port = port;
44 | } else {
45 | throw new IllegalArgumentException(port + " is not in bounds 0 < x < 65536");
46 | }
47 | }
48 | }
49 |
50 |
51 | public SocksAddressType addressType() {
52 | return this.addressType;
53 | }
54 |
55 | public String host() {
56 | return IDN.toUnicode(this.host);
57 | }
58 |
59 | public int port() {
60 | return this.port;
61 | }
62 |
63 | public void encodeAsByteBuf(ByteBuf byteBuf) {
64 | byteBuf.writeByte(this.addressType.byteValue());
65 | switch(this.addressType) {
66 | case IPv4:
67 | byteBuf.writeBytes(NetUtil.createByteArrayFromIpAddressString(this.host));
68 | byteBuf.writeShort(this.port);
69 | break;
70 | case DOMAIN:
71 | byteBuf.writeByte(this.host.length());
72 | byteBuf.writeBytes(this.host.getBytes(CharsetUtil.US_ASCII));
73 | byteBuf.writeShort(this.port);
74 | break;
75 | case IPv6:
76 | byteBuf.writeBytes(NetUtil.createByteArrayFromIpAddressString(this.host));
77 | byteBuf.writeShort(this.port);
78 | }
79 | }
80 |
81 | public static SWAddrRequest getAddrRequest(ByteBuf byteBuf) {
82 | SWAddrRequest request = null;
83 | SocksAddressType addressType = SocksAddressType.valueOf(byteBuf.readByte());
84 | String host;
85 | int port;
86 | switch (addressType) {
87 | case IPv4: {
88 | host = SocksCommonUtils.intToIp(byteBuf.readInt());
89 | port = byteBuf.readUnsignedShort();
90 | request = new SWAddrRequest( addressType, host, port);
91 | break;
92 | }
93 | case DOMAIN: {
94 | int fieldLength = byteBuf.readByte();
95 | host = SocksCommonUtils.readUsAscii(byteBuf, fieldLength);
96 | port = byteBuf.readUnsignedShort();
97 | request = new SWAddrRequest( addressType, host, port);
98 | break;
99 | }
100 | case IPv6: {
101 | byte[] bytes = new byte[16];
102 | byteBuf.readBytes(bytes);
103 | host = SocksCommonUtils.ipv6toStr(bytes);
104 | port = byteBuf.readUnsignedShort();
105 | request = new SWAddrRequest(addressType, host, port);
106 | break;
107 | }
108 | case UNKNOWN:
109 | break;
110 | }
111 | return request;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/sw/SWCommon.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.sw;
2 |
3 | import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
4 | import io.netty.util.AttributeKey;
5 | import cn.wowspeeder.encryption.ICrypt;
6 |
7 | import java.net.InetSocketAddress;
8 |
9 | public class SWCommon {
10 |
11 | public static final AttributeKey RemoteAddr = AttributeKey.valueOf("ssclient");
12 | public static final AttributeKey REMOTE_DES = AttributeKey.valueOf("ssremotedes");
13 | public static final AttributeKey REMOTE_DES_SOCKS5 = AttributeKey.valueOf("socks5remotedes");
14 | public static final AttributeKey PASSWORD = AttributeKey.valueOf("password");
15 |
16 | public static final int TCP_PROXY_IDEL_TIME = 120;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/sw/SWLocalTcpProxyHandler.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.sw;
2 |
3 | import cn.wowspeeder.encryption.Base64Encrypt;
4 | import cn.wowspeeder.websocket.WebSocketClientHandler;
5 | import cn.wowspeeder.websocket.WebSocketLocalFrameHandler;
6 | import io.netty.bootstrap.Bootstrap;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.channel.*;
9 | import io.netty.channel.socket.nio.NioSocketChannel;
10 | import io.netty.handler.codec.http.DefaultHttpHeaders;
11 | import io.netty.handler.codec.http.HttpClientCodec;
12 | import io.netty.handler.codec.http.HttpObjectAggregator;
13 | import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
14 | import io.netty.handler.codec.http.websocketx.WebSocketVersion;
15 | import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
16 | import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
17 | import io.netty.handler.ssl.SslContext;
18 | import io.netty.handler.ssl.SslContextBuilder;
19 | import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
20 | import io.netty.handler.timeout.IdleState;
21 | import io.netty.handler.timeout.IdleStateEvent;
22 | import io.netty.handler.timeout.IdleStateHandler;
23 | import io.netty.util.ReferenceCountUtil;
24 | import io.netty.util.internal.logging.InternalLogger;
25 | import io.netty.util.internal.logging.InternalLoggerFactory;
26 |
27 | import javax.net.ssl.SSLException;
28 | import java.net.InetSocketAddress;
29 | import java.net.URI;
30 | import java.util.ArrayList;
31 | import java.util.List;
32 | import java.util.ListIterator;
33 | import java.util.concurrent.TimeUnit;
34 |
35 | public class SWLocalTcpProxyHandler extends SimpleChannelInboundHandler {
36 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SWServerTcpProxyHandler.class);
37 |
38 | private InetSocketAddress ssServer;
39 | private Socks5CommandRequest remoteAddr;
40 | private Channel clientChannel;
41 | private Channel remoteChannel;
42 | private Bootstrap proxyClient;
43 | private String password;
44 | private List clientBuffs;
45 |
46 |
47 | public SWLocalTcpProxyHandler(String server, Integer port, String password) {
48 | this.password = password;
49 | ssServer = new InetSocketAddress(server, port);
50 | }
51 |
52 | @Override
53 | protected void channelRead0(ChannelHandlerContext clientCtx, ByteBuf msg) throws Exception {
54 |
55 | if (this.clientChannel == null) {
56 | this.clientChannel = clientCtx.channel();
57 | this.remoteAddr = clientChannel.attr(SWCommon.REMOTE_DES_SOCKS5).get();
58 | }
59 | logger.debug("channel id {},readableBytes:{}", clientChannel.id().toString(), msg.readableBytes());
60 | // if (msg.readableBytes() == 0) return;
61 | proxy(clientCtx, msg);
62 | }
63 |
64 | private void proxy(ChannelHandlerContext clientCtx, ByteBuf msg) throws Exception {
65 | logger.debug("channel id {},pc is null {},{}", clientChannel.id().toString(), (remoteChannel == null), msg.readableBytes());
66 | if (remoteChannel == null && proxyClient == null) {
67 | // url = ws://127.0.0.1:8080/websocket?token=123.456.44.7:8080
68 | Base64Encrypt base64 = Base64Encrypt.getInstance(password);
69 |
70 | String url = "ws://"+ssServer.getHostString()+":"+ssServer.getPort()+"/websocket?token="+ base64.getEncString(remoteAddr.dstAddr()+":"+remoteAddr.dstPort());
71 | //If the base64 encoding exceeds 76 characters, it will wrap, replace \n or \r in base64 encoding
72 | logger.info("============"+ remoteAddr.dstAddr() +"====" + url.replaceAll("\r|\n",""));
73 | URI uri = new URI(url.replaceAll("\r|\n",""));
74 |
75 | final SslContext sslCtx = getSslContext(uri);
76 |
77 | // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
78 | // If you change it to V00, ping is not supported and remember to change
79 | // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
80 | final WebSocketClientHandler handler =
81 | new WebSocketClientHandler(
82 | WebSocketClientHandshakerFactory.newHandshaker(
83 | uri, WebSocketVersion.V13, null, true,new DefaultHttpHeaders(), 65536 * 5));
84 |
85 |
86 | proxyClient = new Bootstrap();//
87 | proxyClient.group(clientChannel.eventLoop()).channel(NioSocketChannel.class)
88 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60 * 1000)
89 | .option(ChannelOption.SO_KEEPALIVE, true)
90 | .option(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024)// 接收缓冲区为2M
91 | .option(ChannelOption.SO_SNDBUF, 2 * 1024 * 1024)// 发送缓冲区为2M
92 | .option(ChannelOption.TCP_NODELAY, false)
93 | .handler(
94 | new ChannelInitializer() {
95 | @Override
96 | protected void initChannel(Channel ch) throws Exception {
97 | logger.debug("channel initializer");
98 |
99 | ch.pipeline()
100 | .addLast("timeout", new IdleStateHandler(0, 0, SWCommon.TCP_PROXY_IDEL_TIME, TimeUnit.SECONDS) {
101 | @Override
102 | protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) {
103 | logger.debug("{} state:{}", ssServer.toString(), state.toString());
104 | proxyChannelClose();
105 | return super.newIdleStateEvent(state, first);
106 | }
107 | });
108 |
109 |
110 | //ss-out
111 | ChannelPipeline pipeline = ch.pipeline();
112 | if (sslCtx != null) {
113 | pipeline.addLast(sslCtx.newHandler(ch.alloc(), ssServer.getHostString(), ssServer.getPort()));
114 | }
115 |
116 | pipeline.addLast(new HttpClientCodec())
117 | .addLast(new HttpObjectAggregator(65536 * 5))
118 | .addLast(WebSocketClientCompressionHandler.INSTANCE)
119 | .addLast(handler)
120 | .addLast(new WebSocketLocalFrameHandler());
121 |
122 | // pipeline.addLast("ssCipherCodec", new SSCipherCodec());
123 | pipeline.addLast("relay", new SimpleChannelInboundHandler() {
124 | @Override
125 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
126 | clientChannel.writeAndFlush(msg.retain());
127 | }
128 |
129 | @Override
130 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
131 | // logger.debug("channelActive {}",msg.readableBytes());
132 | }
133 |
134 | @Override
135 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
136 | super.channelInactive(ctx);
137 | proxyChannelClose();
138 | }
139 |
140 | @Override
141 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
142 | // super.exceptionCaught(ctx, cause);
143 | cause.printStackTrace();
144 | proxyChannelClose();
145 | }
146 | })
147 | ;
148 | }
149 | }
150 | );
151 | try {
152 |
153 | proxyClient
154 | .connect(ssServer).addListener((ChannelFutureListener)f -> {
155 |
156 | if(f.isSuccess()){
157 | // logger.debug("channel id {}, {}<->{}<->{} connect {}", clientChannel.id().toString(), clientChannel.remoteAddress().toString(), f.channel().localAddress().toString(), ssServer.toString(), f.isSuccess());
158 | handler.handshakeFuture()
159 | .addListener((ChannelFutureListener) future -> {
160 | try {
161 | if (future.isSuccess()) {
162 | logger.debug("channel id {}, {}<->{}<->{} handshake {}", clientChannel.id().toString(), clientChannel.remoteAddress().toString(), future.channel().localAddress().toString(), ssServer.toString(), future.isSuccess());
163 | remoteChannel = future.channel();
164 |
165 | //write remaining bufs
166 | if (clientBuffs != null) {
167 | ListIterator bufsIterator = clientBuffs.listIterator();
168 | while (bufsIterator.hasNext()) {
169 | remoteChannel.writeAndFlush(bufsIterator.next());
170 | }
171 | clientBuffs = null;
172 | }
173 | } else {
174 | logger.error("channel id {}, {}<->{} handshake {},cause {}", clientChannel.id().toString(), clientChannel.remoteAddress().toString(), ssServer.toString(), future.isSuccess(), future.cause());
175 | proxyChannelClose();
176 | }
177 | } catch (Exception e) {
178 | proxyChannelClose();
179 | }
180 | });
181 | }else{
182 | // logger.error("channel id {}, {}<->{} connect {},cause {}", clientChannel.id().toString(), clientChannel.remoteAddress().toString(), ssServer.toString(), f.isSuccess(), f.cause());
183 | proxyChannelClose();
184 | }
185 |
186 | });
187 |
188 | } catch (Exception e) {
189 | logger.error("connect internet error", e);
190 | proxyChannelClose();
191 | return;
192 | }
193 | }
194 |
195 | if (remoteChannel == null) {
196 | if (clientBuffs == null) {
197 | clientBuffs = new ArrayList<>();
198 | }
199 | clientBuffs.add(msg.retain());//
200 | logger.debug("channel id {},add to client buff list", clientChannel.id().toString());
201 | } else {
202 | if (clientBuffs == null) {
203 | remoteChannel.writeAndFlush(msg.retain());
204 | } else {
205 | clientBuffs.add(msg.retain());//
206 | }
207 | logger.debug("channel id {},remote channel write {}", clientChannel.id().toString(), msg.readableBytes());
208 | }
209 | }
210 |
211 |
212 | @Override
213 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
214 | super.channelInactive(ctx);
215 | proxyChannelClose();
216 | }
217 |
218 | @Override
219 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
220 | // super.exceptionCaught(ctx, cause);
221 | proxyChannelClose();
222 | }
223 |
224 | private SslContext getSslContext(URI uri) throws SSLException {
225 | SslContext sslCtx;
226 |
227 | String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
228 |
229 | if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
230 | System.err.println("Only WS(S) is supported.");
231 | return null;
232 | }
233 |
234 | boolean ssl = "wss".equalsIgnoreCase(scheme);
235 |
236 | if (ssl) {
237 | sslCtx = SslContextBuilder.forClient()
238 | .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
239 | } else {
240 | sslCtx = null;
241 | }
242 | return sslCtx;
243 | }
244 |
245 |
246 | private void proxyChannelClose() {
247 | // logger.info("proxyChannelClose");
248 | try {
249 | if (clientBuffs != null) {
250 | clientBuffs.forEach(ReferenceCountUtil::release);
251 | clientBuffs = null;
252 | }
253 | if (remoteChannel != null) {
254 | remoteChannel.close();
255 | remoteChannel = null;
256 | }
257 | if (clientChannel != null) {
258 | clientChannel.close();
259 | clientChannel = null;
260 | }
261 | } catch (Exception e) {
262 | logger.error("close channel error", e);
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/main/java/cn/wowspeeder/sw/SWServerCheckerReceive.java:
--------------------------------------------------------------------------------
1 | package cn.wowspeeder.sw;
2 |
3 | import io.netty.channel.ChannelHandlerContext;
4 | import io.netty.channel.SimpleChannelInboundHandler;
5 | import io.netty.channel.socket.DatagramPacket;
6 | import io.netty.util.internal.logging.InternalLogger;
7 | import io.netty.util.internal.logging.InternalLoggerFactory;
8 |
9 | import java.net.InetSocketAddress;
10 |
11 | public class SWServerCheckerReceive extends SimpleChannelInboundHandler