├── .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 | ![image](https://img-blog.csdnimg.cn/2020051017110683.png) 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 { 12 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SWServerCheckerReceive.class); 13 | 14 | public SWServerCheckerReceive() { 15 | super(false); 16 | } 17 | 18 | @Override 19 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 20 | super.channelActive(ctx); 21 | } 22 | 23 | @Override 24 | protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 25 | 26 | ctx.channel().attr(SWCommon.RemoteAddr).set((InetSocketAddress) ctx.channel().remoteAddress()); 27 | ctx.channel().pipeline().remove(this); 28 | ctx.fireChannelRead(msg); 29 | 30 | } 31 | 32 | @Override 33 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 34 | super.exceptionCaught(ctx, cause); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/sw/SWServerCheckerSend.java: -------------------------------------------------------------------------------- 1 | package cn.wowspeeder.sw; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelOutboundHandlerAdapter; 6 | import io.netty.channel.ChannelPromise; 7 | import io.netty.channel.socket.DatagramPacket; 8 | import io.netty.util.internal.logging.InternalLogger; 9 | import io.netty.util.internal.logging.InternalLoggerFactory; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | public class SWServerCheckerSend extends ChannelOutboundHandlerAdapter { 14 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SWServerCheckerSend.class); 15 | 16 | @Override 17 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 18 | 19 | super.write(ctx,msg,promise); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/sw/SWServerTcpProxyHandler.java: -------------------------------------------------------------------------------- 1 | package cn.wowspeeder.sw; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.nio.NioSocketChannel; 7 | import io.netty.handler.timeout.IdleState; 8 | import io.netty.handler.timeout.IdleStateEvent; 9 | import io.netty.handler.timeout.IdleStateHandler; 10 | import io.netty.util.ReferenceCountUtil; 11 | import io.netty.util.internal.logging.InternalLogger; 12 | import io.netty.util.internal.logging.InternalLoggerFactory; 13 | 14 | import java.net.InetSocketAddress; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.ListIterator; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class SWServerTcpProxyHandler extends SimpleChannelInboundHandler { 21 | private static InternalLogger logger = InternalLoggerFactory.getInstance(SWServerTcpProxyHandler.class); 22 | 23 | private Channel clientChannel; 24 | private Channel remoteChannel; 25 | private Bootstrap proxyClient; 26 | private List clientBuffs; 27 | 28 | public SWServerTcpProxyHandler() { 29 | } 30 | 31 | 32 | @Override 33 | protected void channelRead0(ChannelHandlerContext clientCtx, ByteBuf msg) throws Exception { 34 | 35 | if (this.clientChannel == null) { 36 | this.clientChannel = clientCtx.channel(); 37 | } 38 | logger.debug("channel id {},readableBytes:{}", clientChannel.id().toString(), msg.readableBytes()); 39 | // if (msg.readableBytes() == 0) return; 40 | proxy(clientCtx, msg); 41 | } 42 | 43 | private void proxy(ChannelHandlerContext clientCtx, ByteBuf msg) { 44 | logger.debug("channel id {},pc is null {},{}", clientChannel.id().toString(), (remoteChannel == null), msg.readableBytes()); 45 | if (remoteChannel == null && proxyClient == null) { 46 | proxyClient = new Bootstrap();// 47 | InetSocketAddress clientRecipient = clientChannel.attr(SWCommon.REMOTE_DES).get(); 48 | 49 | proxyClient.group(clientChannel.eventLoop()).channel(NioSocketChannel.class) 50 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60 * 1000) 51 | .option(ChannelOption.SO_KEEPALIVE, true) 52 | .option(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024)// 读缓冲区为2M 53 | .option(ChannelOption.SO_SNDBUF, 2 * 1024 * 1024)// 发送缓冲区为2M 54 | .option(ChannelOption.TCP_NODELAY, false) 55 | .handler( 56 | new ChannelInitializer() { 57 | @Override 58 | protected void initChannel(Channel ch) throws Exception { 59 | ch.pipeline() 60 | .addLast("timeout", new IdleStateHandler(0, 0, SWCommon.TCP_PROXY_IDEL_TIME, TimeUnit.SECONDS) { 61 | @Override 62 | protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) { 63 | logger.debug("{} state:{}", clientRecipient.toString(), state.toString()); 64 | proxyChannelClose(); 65 | return super.newIdleStateEvent(state, first); 66 | } 67 | }) 68 | .addLast("tcpProxy", new SimpleChannelInboundHandler() { 69 | @Override 70 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 71 | clientChannel.writeAndFlush(msg.retain()); 72 | } 73 | 74 | @Override 75 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 76 | // logger.debug("channelActive {}",msg.readableBytes()); 77 | } 78 | 79 | @Override 80 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 81 | super.channelInactive(ctx); 82 | proxyChannelClose(); 83 | } 84 | 85 | @Override 86 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 87 | // super.exceptionCaught(ctx, cause); 88 | proxyChannelClose(); 89 | } 90 | }); 91 | } 92 | } 93 | ); 94 | try { 95 | proxyClient 96 | .connect(clientRecipient) 97 | .addListener((ChannelFutureListener) future -> { 98 | try { 99 | if (future.isSuccess()) { 100 | logger.debug("channel id {}, {}<->{}<->{} connect {}", clientChannel.id().toString(), clientChannel.remoteAddress().toString(), future.channel().localAddress().toString(), clientRecipient.toString(), future.isSuccess()); 101 | remoteChannel = future.channel(); 102 | if (clientBuffs != null) { 103 | ListIterator bufsIterator = clientBuffs.listIterator(); 104 | while (bufsIterator.hasNext()) { 105 | remoteChannel.writeAndFlush(bufsIterator.next()); 106 | } 107 | clientBuffs = null; 108 | } 109 | } else { 110 | logger.error("channel id {}, {}<->{} connect {},cause {}", clientChannel.id().toString(), clientChannel.remoteAddress().toString(), clientRecipient.toString(), future.isSuccess(), future.cause()); 111 | proxyChannelClose(); 112 | } 113 | } catch (Exception e) { 114 | proxyChannelClose(); 115 | } 116 | }); 117 | } catch (Exception e) { 118 | logger.error("connect internet error", e); 119 | proxyChannelClose(); 120 | return; 121 | } 122 | } 123 | 124 | if (remoteChannel == null) { 125 | if (clientBuffs == null) { 126 | clientBuffs = new ArrayList<>(); 127 | } 128 | clientBuffs.add(msg.retain()); 129 | // logger.debug("channel id {},add to client buff list", clientChannel.id().toString()); 130 | } else { 131 | if (clientBuffs == null) { 132 | remoteChannel.writeAndFlush(msg.retain()); 133 | } else { 134 | clientBuffs.add(msg.retain()); 135 | } 136 | // logger.debug("channel id {},remote channel write {}", clientChannel.id().toString(), msg.readableBytes()); 137 | } 138 | } 139 | 140 | 141 | @Override 142 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 143 | super.channelInactive(ctx); 144 | proxyChannelClose(); 145 | } 146 | 147 | @Override 148 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 149 | // super.exceptionCaught(ctx,cause); 150 | cause.printStackTrace(); 151 | proxyChannelClose(); 152 | } 153 | 154 | private void proxyChannelClose() { 155 | // logger.info("proxyChannelClose"); 156 | try { 157 | if (clientBuffs != null) { 158 | clientBuffs.forEach(ReferenceCountUtil::release); 159 | clientBuffs = null; 160 | } 161 | if (remoteChannel != null) { 162 | remoteChannel.close(); 163 | remoteChannel = null; 164 | } 165 | if (clientChannel != null) { 166 | clientChannel.close(); 167 | clientChannel = null; 168 | } 169 | } catch (Exception e) { 170 | // logger.error("close channel error", e); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/sw/SocksCommonUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package cn.wowspeeder.sw; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.handler.codec.socks.SocksRequest; 20 | import io.netty.handler.codec.socks.SocksResponse; 21 | import io.netty.handler.codec.socks.UnknownSocksRequest; 22 | import io.netty.handler.codec.socks.UnknownSocksResponse; 23 | import io.netty.util.CharsetUtil; 24 | import io.netty.util.internal.StringUtil; 25 | 26 | /** 27 | * copy from io.netty.handler.codec.socks.SocksCommonUtils 28 | */ 29 | final class SocksCommonUtils { 30 | public static final SocksRequest UNKNOWN_SOCKS_REQUEST = new UnknownSocksRequest(); 31 | public static final SocksResponse UNKNOWN_SOCKS_RESPONSE = new UnknownSocksResponse(); 32 | 33 | private static final int SECOND_ADDRESS_OCTET_SHIFT = 16; 34 | private static final int FIRST_ADDRESS_OCTET_SHIFT = 24; 35 | private static final int THIRD_ADDRESS_OCTET_SHIFT = 8; 36 | private static final int XOR_DEFAULT_VALUE = 0xff; 37 | 38 | /** 39 | * A constructor to stop this class being constructed. 40 | */ 41 | private SocksCommonUtils() { 42 | // NOOP 43 | } 44 | 45 | public static String intToIp(int i) { 46 | return String.valueOf(i >> FIRST_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + 47 | (i >> SECOND_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + 48 | (i >> THIRD_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + 49 | (i & XOR_DEFAULT_VALUE); 50 | } 51 | 52 | private static final char[] ipv6conseqZeroFiller = {':', ':'}; 53 | private static final char ipv6hextetSeparator = ':'; 54 | 55 | /** 56 | * Convert numeric IPv6 to compressed format, where 57 | * the longest sequence of 0's (with 2 or more 0's) is replaced with "::" 58 | */ 59 | public static String ipv6toCompressedForm(byte[] src) { 60 | assert src.length == 16; 61 | //Find the longest sequence of 0's 62 | //start of compressed region (hextet index) 63 | int cmprHextet = -1; 64 | //length of compressed region 65 | int cmprSize = 0; 66 | for (int hextet = 0; hextet < 8;) { 67 | int curByte = hextet * 2; 68 | int size = 0; 69 | while (curByte < src.length && src[curByte] == 0 70 | && src[curByte + 1] == 0) { 71 | curByte += 2; 72 | size++; 73 | } 74 | if (size > cmprSize) { 75 | cmprHextet = hextet; 76 | cmprSize = size; 77 | } 78 | hextet = curByte / 2 + 1; 79 | } 80 | if (cmprHextet == -1 || cmprSize < 2) { 81 | //No compression can be applied 82 | return ipv6toStr(src); 83 | } 84 | StringBuilder sb = new StringBuilder(39); 85 | ipv6toStr(sb, src, 0, cmprHextet); 86 | sb.append(ipv6conseqZeroFiller); 87 | ipv6toStr(sb, src, cmprHextet + cmprSize, 8); 88 | return sb.toString(); 89 | } 90 | 91 | /** 92 | * Converts numeric IPv6 to standard (non-compressed) format. 93 | */ 94 | public static String ipv6toStr(byte[] src) { 95 | assert src.length == 16; 96 | StringBuilder sb = new StringBuilder(39); 97 | ipv6toStr(sb, src, 0, 8); 98 | return sb.toString(); 99 | } 100 | 101 | private static void ipv6toStr(StringBuilder sb, byte[] src, int fromHextet, int toHextet) { 102 | int i; 103 | toHextet --; 104 | for (i = fromHextet; i < toHextet; i++) { 105 | appendHextet(sb, src, i); 106 | sb.append(ipv6hextetSeparator); 107 | } 108 | 109 | appendHextet(sb, src, i); 110 | } 111 | 112 | private static void appendHextet(StringBuilder sb, byte[] src, int i) { 113 | StringUtil.toHexString(sb, src, i << 1, 2); 114 | } 115 | 116 | static String readUsAscii(ByteBuf buffer, int length) { 117 | String s = buffer.toString(buffer.readerIndex(), length, CharsetUtil.US_ASCII); 118 | buffer.skipBytes(length); 119 | return s; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/HttpHandShakeRequestHandler.java: -------------------------------------------------------------------------------- 1 | package cn.wowspeeder.websocket; 2 | 3 | import cn.wowspeeder.encryption.Base64Encrypt; 4 | import cn.wowspeeder.sw.SWCommon; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import io.netty.handler.codec.http.HttpRequest; 8 | import io.netty.util.internal.logging.InternalLogger; 9 | import io.netty.util.internal.logging.InternalLoggerFactory; 10 | 11 | import java.net.InetSocketAddress; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class HttpHandShakeRequestHandler extends ChannelInboundHandlerAdapter { 16 | private static InternalLogger logger = InternalLoggerFactory.getInstance(HttpHandShakeRequestHandler.class); 17 | 18 | @Override 19 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 20 | 21 | if(msg instanceof HttpRequest){ 22 | String targetAddr = ""; 23 | HttpRequest request = (HttpRequest)msg; 24 | String uri = request.uri(); 25 | // 26 | if ("/".equals(uri) || "/index.html".equals(uri)) { 27 | // No need to parse args 28 | ctx.fireChannelRead(msg); 29 | return; 30 | } 31 | // parse args to get targetHost and targetPort 32 | String[] s = uri.split("\\?"); 33 | switch (s.length){ 34 | case 1: 35 | // 36 | throw new UnsupportedOperationException("url args not right please check "); 37 | case 2: 38 | //remove the part after ? and reset uri 39 | // uri = /websocket?target=123.456.44.7:8080 40 | request.setUri(s[0]); 41 | String token = s[1]; 42 | 43 | Pattern tp = Pattern.compile("^token=(.*)"); 44 | Matcher tm = tp.matcher(token); 45 | if (tm.matches()) { 46 | targetAddr = tm.group(1); 47 | }else { 48 | logger.error("url args not right please check"); 49 | throw new UnsupportedOperationException("url args not right please check "); 50 | } 51 | 52 | String password = ctx.channel().attr(SWCommon.PASSWORD).get(); 53 | Base64Encrypt base64 = Base64Encrypt.getInstance(password); 54 | 55 | String targetHostAndPort = base64.getDesString(targetAddr); 56 | //eg: targetHostAndPort = www.baidu.com:443 57 | Pattern p = Pattern.compile("^\\s*(.*?):(\\d+)\\s*$"); 58 | Matcher m = p.matcher(targetHostAndPort); 59 | if (m.matches()) { 60 | String host = m.group(1); 61 | int port = Integer.parseInt(m.group(2)); 62 | ctx.channel().attr(SWCommon.REMOTE_DES).set(new InetSocketAddress(host, port)); 63 | }else { 64 | logger.error("may be password is not right"); 65 | throw new UnsupportedOperationException("may be password is not right"); 66 | } 67 | 68 | break; 69 | default: 70 | logger.warn("More than one ? in url: {}", uri); 71 | throw new UnsupportedOperationException("url args not right please check "); 72 | } 73 | } 74 | //remove this pipline, bacause only one HttpReqest in In one websocket request process 75 | 76 | ctx.fireChannelRead(msg); 77 | ctx.pipeline().remove(this); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/WebSocketClientHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | //The MIT License 17 | // 18 | //Copyright (c) 2009 Carl Bystršm 19 | // 20 | //Permission is hereby granted, free of charge, to any person obtaining a copy 21 | //of this software and associated documentation files (the "Software"), to deal 22 | //in the Software without restriction, including without limitation the rights 23 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | //copies of the Software, and to permit persons to whom the Software is 25 | //furnished to do so, subject to the following conditions: 26 | // 27 | //The above copyright notice and this permission notice shall be included in 28 | //all copies or substantial portions of the Software. 29 | // 30 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | //THE SOFTWARE. 37 | 38 | package cn.wowspeeder.websocket; 39 | 40 | import io.netty.channel.*; 41 | import io.netty.handler.codec.UnsupportedMessageTypeException; 42 | import io.netty.handler.codec.http.FullHttpResponse; 43 | import io.netty.handler.codec.http.websocketx.*; 44 | import io.netty.util.CharsetUtil; 45 | import io.netty.util.internal.logging.InternalLogger; 46 | import io.netty.util.internal.logging.InternalLoggerFactory; 47 | 48 | public class WebSocketClientHandler extends SimpleChannelInboundHandler { 49 | private static InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandler.class); 50 | 51 | private final WebSocketClientHandshaker handshaker; 52 | private ChannelPromise handshakeFuture; 53 | 54 | public WebSocketClientHandler(WebSocketClientHandshaker handshaker) { 55 | this.handshaker = handshaker; 56 | } 57 | 58 | public ChannelFuture handshakeFuture() { 59 | return handshakeFuture; 60 | } 61 | 62 | @Override 63 | public void handlerAdded(ChannelHandlerContext ctx) { 64 | handshakeFuture = ctx.newPromise(); 65 | } 66 | 67 | @Override 68 | public void channelActive(ChannelHandlerContext ctx) { 69 | 70 | handshaker.handshake(ctx.channel()); 71 | 72 | } 73 | 74 | @Override 75 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 76 | logger.debug("WebSocket Client disconnected!"); 77 | super.channelInactive(ctx); 78 | } 79 | 80 | @Override 81 | public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 82 | Channel ch = ctx.channel(); 83 | if (!handshaker.isHandshakeComplete()) { 84 | try { 85 | handshaker.finishHandshake(ch, (FullHttpResponse) msg); 86 | logger.debug("WebSocket Client connected!"); 87 | handshakeFuture.setSuccess(); 88 | } catch (WebSocketHandshakeException e) { 89 | logger.debug("WebSocket Client failed to connect"); 90 | handshakeFuture.setFailure(e); 91 | } 92 | return; 93 | } 94 | 95 | if (msg instanceof FullHttpResponse) { 96 | FullHttpResponse response = (FullHttpResponse) msg; 97 | throw new IllegalStateException( 98 | "Unexpected FullHttpResponse (getStatus=" + response.status() + 99 | ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); 100 | } 101 | 102 | WebSocketFrame frame = (WebSocketFrame) msg; 103 | if (frame instanceof BinaryWebSocketFrame) { 104 | ctx.fireChannelRead(frame); 105 | } else if (frame instanceof PongWebSocketFrame) { 106 | logger.debug("WebSocket Client received pong"); 107 | } else if (frame instanceof CloseWebSocketFrame) { 108 | logger.debug("WebSocket Client received closing"); 109 | frame.release(); 110 | ch.close(); 111 | }else { 112 | logger.error("Not support message of sub-class WebSocketFrame: {}", msg.toString()); 113 | throw new UnsupportedMessageTypeException("not support message of sub-class WebSocketFrame"); 114 | } 115 | } 116 | 117 | @Override 118 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 119 | // cause.printStackTrace(); 120 | if (!handshakeFuture.isDone()) { 121 | handshakeFuture.setFailure(cause); 122 | } 123 | ctx.fireExceptionCaught(cause); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/WebSocketIndexPageHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package cn.wowspeeder.websocket; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.ByteBufUtil; 20 | import io.netty.channel.*; 21 | import io.netty.handler.codec.http.*; 22 | import io.netty.handler.ssl.SslHandler; 23 | 24 | import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; 25 | import static io.netty.handler.codec.http.HttpMethod.GET; 26 | import static io.netty.handler.codec.http.HttpResponseStatus.*; 27 | 28 | /** 29 | * Outputs index page content. 30 | */ 31 | public class WebSocketIndexPageHandler extends SimpleChannelInboundHandler { 32 | 33 | private final String websocketPath; 34 | 35 | public WebSocketIndexPageHandler(String websocketPath) { 36 | this.websocketPath = websocketPath; 37 | } 38 | 39 | @Override 40 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { 41 | System.out.println("======WebSocketIndexPageHandler"); 42 | // Handle a bad request. 43 | if (!req.decoderResult().isSuccess()) { 44 | sendHttpResponse(ctx, req, new DefaultFullHttpResponse(req.protocolVersion(), BAD_REQUEST, 45 | ctx.alloc().buffer(0))); 46 | return; 47 | } 48 | 49 | // Allow only GET methods. 50 | if (!GET.equals(req.method())) { 51 | sendHttpResponse(ctx, req, new DefaultFullHttpResponse(req.protocolVersion(), FORBIDDEN, 52 | ctx.alloc().buffer(0))); 53 | return; 54 | } 55 | 56 | // Send the index page 57 | if ("/".equals(req.uri()) || "/index.html".equals(req.uri())) { 58 | String webSocketLocation = getWebSocketLocation(ctx.pipeline(), req, websocketPath); 59 | ByteBuf content = WebSocketServerIndexPage.getContent(webSocketLocation); 60 | FullHttpResponse res = new DefaultFullHttpResponse(req.protocolVersion(), OK, content); 61 | 62 | res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); 63 | HttpUtil.setContentLength(res, content.readableBytes()); 64 | 65 | sendHttpResponse(ctx, req, res); 66 | } else { 67 | sendHttpResponse(ctx, req, new DefaultFullHttpResponse(req.protocolVersion(), NOT_FOUND, 68 | ctx.alloc().buffer(0))); 69 | 70 | } 71 | } 72 | 73 | @Override 74 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 75 | // cause.printStackTrace(); 76 | // ctx.close(); 77 | super.exceptionCaught(ctx, cause); 78 | } 79 | 80 | private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { 81 | // Generate an error page if response getStatus code is not OK (200). 82 | HttpResponseStatus responseStatus = res.status(); 83 | if (responseStatus.code() != 200) { 84 | ByteBufUtil.writeUtf8(res.content(), responseStatus.toString()); 85 | HttpUtil.setContentLength(res, res.content().readableBytes()); 86 | } 87 | // Send the response and close the connection if necessary. 88 | boolean keepAlive = HttpUtil.isKeepAlive(req) && responseStatus.code() == 200; 89 | HttpUtil.setKeepAlive(res, keepAlive); 90 | ChannelFuture future = ctx.writeAndFlush(res); 91 | if (!keepAlive) { 92 | future.addListener(ChannelFutureListener.CLOSE); 93 | } 94 | } 95 | 96 | private static String getWebSocketLocation(ChannelPipeline cp, HttpRequest req, String path) { 97 | String protocol = "ws"; 98 | if (cp.get(SslHandler.class) != null) { 99 | // SSL in use so use Secure WebSockets 100 | protocol = "wss"; 101 | } 102 | return protocol + "://" + req.headers().get(HttpHeaderNames.HOST) + path; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/WebSocketLocalFrameHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package cn.wowspeeder.websocket; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.handler.codec.MessageToMessageCodec; 21 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 22 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * Echoes uppercase content of text frames. 30 | */ 31 | public class WebSocketLocalFrameHandler extends MessageToMessageCodec { 32 | 33 | private static InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketLocalFrameHandler.class); 34 | 35 | @Override 36 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 37 | //transfer to WebSocketFrame 38 | 39 | out.add(new BinaryWebSocketFrame(msg).retain()); 40 | } 41 | 42 | @Override 43 | protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List out) throws Exception { 44 | //transfer to ByteBuf 45 | 46 | out.add(msg.content().retain().retain()); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/WebSocketServerFrameHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package cn.wowspeeder.websocket; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.handler.codec.MessageToMessageCodec; 21 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 22 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 23 | import io.netty.util.internal.logging.InternalLogger; 24 | import io.netty.util.internal.logging.InternalLoggerFactory; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * Echoes uppercase content of text frames. 30 | */ 31 | public class WebSocketServerFrameHandler extends MessageToMessageCodec { 32 | 33 | private static InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerFrameHandler.class); 34 | 35 | @Override 36 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 37 | //transfer to WebSocketFrame 38 | 39 | out.add(new BinaryWebSocketFrame(msg).retain()); 40 | } 41 | 42 | @Override 43 | protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List out) throws Exception { 44 | //transfer to ByteBuf 45 | 46 | out.add(msg.content().retain()); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/WebSocketServerIndexPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package cn.wowspeeder.websocket; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.Unpooled; 20 | import io.netty.util.CharsetUtil; 21 | 22 | /** 23 | * Generates the demo HTML page which is served at http://localhost:8080/ 24 | */ 25 | public final class WebSocketServerIndexPage { 26 | 27 | private static final String NEWLINE = "\r\n"; 28 | 29 | public static ByteBuf getContent(String webSocketLocation) { 30 | return Unpooled.copiedBuffer( 31 | "Web Socket Test" + NEWLINE + 32 | "" + NEWLINE + 33 | "" + NEWLINE + 65 | "
" + NEWLINE + 66 | "" + 67 | "" + NEWLINE + 69 | "

Output

" + NEWLINE + 70 | "" + NEWLINE + 71 | "
" + NEWLINE + 72 | "" + NEWLINE + 73 | "" + NEWLINE, CharsetUtil.US_ASCII); 74 | } 75 | 76 | private WebSocketServerIndexPage() { 77 | // Unused 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/wowspeeder/websocket/WebSocketServerInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package cn.wowspeeder.websocket; 17 | 18 | import io.netty.channel.ChannelInitializer; 19 | import io.netty.channel.ChannelPipeline; 20 | import io.netty.channel.socket.SocketChannel; 21 | import io.netty.handler.codec.http.HttpObjectAggregator; 22 | import io.netty.handler.codec.http.HttpServerCodec; 23 | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 24 | import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; 25 | import io.netty.handler.ssl.SslContext; 26 | 27 | /** 28 | */ 29 | public class WebSocketServerInitializer extends ChannelInitializer { 30 | 31 | private static final String WEBSOCKET_PATH = "/websocket"; 32 | 33 | private final SslContext sslCtx; 34 | 35 | public WebSocketServerInitializer(SslContext sslCtx) { 36 | this.sslCtx = sslCtx; 37 | } 38 | 39 | @Override 40 | public void initChannel(SocketChannel ch) throws Exception { 41 | ChannelPipeline pipeline = ch.pipeline(); 42 | if (sslCtx != null) { 43 | pipeline.addLast(sslCtx.newHandler(ch.alloc())); 44 | } 45 | pipeline.addLast(new HttpServerCodec()); 46 | pipeline.addLast(new HttpObjectAggregator(65536 * 5)); 47 | pipeline.addLast(new HttpHandShakeRequestHandler()); 48 | pipeline.addLast(new WebSocketServerCompressionHandler()); 49 | pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true, 65536 * 5)); 50 | pipeline.addLast(new WebSocketIndexPageHandler(WEBSOCKET_PATH)); 51 | pipeline.addLast(new WebSocketServerFrameHandler()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | %d{yyyy-MM-dd HH:mm:ss} [%t] %L::: %-5p %C - %m%n 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/package.xml: -------------------------------------------------------------------------------- 1 | 2 | bin 3 | 4 | 5 | tar.gz 6 | 7 | 8 | 9 | 10 | 11 | 12 | false 13 | lib 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | bin 22 | bin 23 | 0755 24 | 25 | *.sh 26 | 27 | 28 | 29 | bin 30 | bin 31 | 32 | *.bat 33 | 34 | 35 | 36 | conf 37 | conf 38 | 39 | * 40 | 41 | 42 | 43 | 44 | 45 | 46 | *.properties 47 | LICENSE 48 | README.md 49 | 50 | 51 | 52 | 53 | 54 | ${project.build.directory} 55 | lib 56 | 57 | *.jar 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/test/java/cn/wowspeeder/sw/SSLocalTest.java: -------------------------------------------------------------------------------- 1 | package cn.wowspeeder.sw; 2 | 3 | import cn.wowspeeder.SWLocal; 4 | import io.netty.util.ResourceLeakDetector; 5 | 6 | public class SSLocalTest { 7 | public static void main(String[] args) { 8 | ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED); 9 | try { 10 | SWLocal.getInstance().start("./conf/config-example-client.json"); 11 | } catch (Exception e) { 12 | e.printStackTrace(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/cn/wowspeeder/sw/SSServerTest.java: -------------------------------------------------------------------------------- 1 | package cn.wowspeeder.sw; 2 | 3 | import cn.wowspeeder.SWServer; 4 | import io.netty.util.ResourceLeakDetector; 5 | 6 | public class SSServerTest { 7 | 8 | public static void main(String[] args) { 9 | ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED); 10 | try { 11 | SWServer.getInstance().start("./conf/config-example-server.json"); 12 | } catch (Exception e) { 13 | e.printStackTrace(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/cn/wowspeeder/sw/SocksCommonUtilsTest.java: -------------------------------------------------------------------------------- 1 | package cn.wowspeeder.sw; 2 | 3 | import cn.wowspeeder.sw.SocksCommonUtils; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | public class SocksCommonUtilsTest { 10 | 11 | @Test 12 | public void testIntToIp() { 13 | Assert.assertEquals("11.8.0.8", 14 | SocksCommonUtils.intToIp(185_073_672)); 15 | } 16 | 17 | @Test 18 | public void testIpv6toCompressedForm() { 19 | byte[] bytes = new byte[]{ 20 | 1, 2, 4, 8, 16, 32, 64, 127, -1, -2, 21 | -4, -16, -32, -64, -128, 0}; 22 | 23 | Assert.assertEquals( 24 | "102:408:1020:407f:fffe:fcf0:e0c0:8000", 25 | SocksCommonUtils.ipv6toCompressedForm(bytes) 26 | ); 27 | } 28 | 29 | @Test 30 | public void testIpv6toCompressedFormCompressionApplied() { 31 | byte[] bytes = new byte[]{ 32 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | 0, 0, 2, 4, 8, 16, 32}; 34 | 35 | Assert.assertEquals( 36 | "0::2:408:1020", 37 | SocksCommonUtils.ipv6toCompressedForm(bytes) 38 | ); 39 | } 40 | 41 | @Test 42 | public void testIpv6toStr() { 43 | byte[] bytes = new byte[]{ 44 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 45 | 0, 0, 2, 4, 8, 16, 32}; 46 | 47 | Assert.assertEquals( 48 | "0:0:0:0:0:2:408:1020", 49 | SocksCommonUtils.ipv6toStr(bytes)); 50 | } 51 | 52 | @Test 53 | public void testReadUsAscii() { 54 | final ByteBuf buffer = Unpooled.copiedBuffer( 55 | new byte[]{'f', 'o', 'o'}); 56 | 57 | Assert.assertEquals( 58 | "foo", 59 | SocksCommonUtils.readUsAscii(buffer, 3) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/test/Base64EncryptTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import cn.wowspeeder.encryption.Base64Encrypt; 4 | 5 | public class Base64EncryptTest { 6 | public static void main(String[] args) throws Exception { 7 | 8 | Base64Encrypt base64 = Base64Encrypt.getInstance("gghggh"); 9 | 10 | String encString = base64.getEncString("133.333.334.456:80"); 11 | 12 | System.out.println(encString); 13 | 14 | System.out.println(base64.getDesString(encString)); 15 | 16 | Base64Encrypt base642 = Base64Encrypt.getInstance("55555"); 17 | 18 | String encString2 = base64.getEncString("13.333.334.666:443"); 19 | 20 | System.out.println(encString2); 21 | 22 | System.out.println(base642.getDesString(encString2)); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/test/ByteBufWriteBytes.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | 6 | public class ByteBufWriteBytes { 7 | public static void main(String[] args) { 8 | 9 | ByteBuf buf = Unpooled.wrappedBuffer("jjgjggg".getBytes(),"s".getBytes()); 10 | byte[] bytes = "sdfsdfsjjjj9999999999999999j".getBytes(); 11 | 12 | System.out.println("11111111"); 13 | buf.retain().clear().writeBytes(bytes); 14 | System.out.println("222"+buf.capacity()); 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/test/GetHostAndPortTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class GetHostAndPortTest { 7 | public static void main(String[] args) { 8 | 9 | Pattern p = Pattern.compile("^\\s*(.*?):(\\d+)\\s*$"); 10 | Matcher m = p.matcher("wwww.baidu.com:8080"); 11 | if (m.matches()) { 12 | String host = m.group(1); 13 | int port = Integer.parseInt(m.group(2)); 14 | System.out.println(host); 15 | System.out.println(port); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/test/PatternTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class PatternTest { 7 | public static void main(String[] args) { 8 | 9 | Pattern p = Pattern.compile("^token=(.*)"); 10 | Matcher m = p.matcher("token=nsw3L0+xrEssTSHMKZD97A=="); 11 | if (m.matches()) { 12 | System.out.println(m.group(1)); 13 | }else { 14 | System.out.println("not mm"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/test/Socks5CommandRequestTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.handler.codec.socksx.v5.*; 5 | 6 | public class Socks5CommandRequestTest { 7 | public static void main(String[] args) { 8 | 9 | DefaultSocks5CommandRequest req = new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, Socks5AddressType.IPv4,"dd222", 8080); 10 | 11 | 12 | } 13 | } 14 | --------------------------------------------------------------------------------