├── .gitignore
├── README.md
├── http-proxy
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── cc
│ │ └── leevi
│ │ └── common
│ │ └── httpproxy
│ │ ├── DirectClientHandler.java
│ │ ├── HttpProxyRequestHead.java
│ │ ├── HttpProxyServerInitializer.java
│ │ ├── HttpServer.java
│ │ ├── HttpServerConnectHandler.java
│ │ ├── HttpServerHeadDecoder.java
│ │ ├── HttpServerUtils.java
│ │ └── RelayHandler.java
│ └── test
│ ├── java
│ └── cc
│ │ └── leevi
│ │ └── common
│ │ └── httpproxy
│ │ └── HttpProxyClientTest.java
│ └── resources
│ └── log4j2.xml
├── mixin-proxy
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── cc
│ │ └── leevi
│ │ └── common
│ │ └── socks5proxy
│ │ ├── DirectClientHandler.java
│ │ ├── HttpProxyRequestHead.java
│ │ ├── HttpServerConnectHandler.java
│ │ ├── HttpServerHeadDecoder.java
│ │ ├── MixinProxyServer.java
│ │ ├── MixinSelectHandler.java
│ │ ├── MixinServerInitializer.java
│ │ ├── MixinServerUtils.java
│ │ ├── RelayHandler.java
│ │ ├── SocksServerConnectHandler.java
│ │ └── SocksServerHandler.java
│ └── test
│ ├── java
│ └── cc
│ │ └── leevi
│ │ └── common
│ │ └── socks5proxy
│ │ └── MixinProxyServerTest.java
│ └── resources
│ └── log4j2.xml
├── pom.xml
└── socks5-proxy
├── pom.xml
└── src
├── main
└── java
│ └── cc
│ └── leevi
│ └── common
│ └── socks5proxy
│ ├── DirectClientHandler.java
│ ├── RelayHandler.java
│ ├── Socks5ProxyServer.java
│ ├── SocksServerConnectHandler.java
│ ├── SocksServerHandler.java
│ ├── SocksServerInitializer.java
│ └── SocksServerUtils.java
└── test
├── java
└── cc
│ └── leevi
│ └── common
│ └── socks5proxy
│ └── Socks5ProxyServerTest.java
└── resources
└── log4j2.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .idea/*
3 | *.iml
4 | target/
5 | *.class
6 | .project
7 | .settings/
8 | .settings/*
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # netty-proxy-server
2 | 基于Netty实现的代理服务器,Web Proxy Server(普通Web代理和SSL隧道代理),Socks5 Proxy Server和混合模式(同时支持以上两种,自动选择)
3 |
4 |
5 | 虽然是个玩具,但麻雀虽小五脏俱全,基本的都有。不管是用来做 netty 的学习,还是代理协议的学习都是不错的参考资料
6 |
--------------------------------------------------------------------------------
/http-proxy/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | netty-proxy-server
7 | cc.leevi.common
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | http-proxy
13 |
14 |
15 |
16 | org.apache.maven.plugins
17 | maven-compiler-plugin
18 |
19 | 7
20 | 7
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | io.netty
29 | netty-all
30 |
31 |
32 | org.apache.commons
33 | commons-lang3
34 |
35 |
36 | org.slf4j
37 | slf4j-api
38 |
39 |
40 | junit
41 | junit
42 | 4.13.1
43 | test
44 |
45 |
46 | org.apache.logging.log4j
47 | log4j-core
48 | 2.14.0
49 | test
50 |
51 |
52 | org.apache.logging.log4j
53 | log4j-slf4j-impl
54 | 2.14.0
55 | test
56 |
57 |
58 | com.google.guava
59 | guava
60 | 30.0-jre
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/DirectClientHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.channel.ChannelInboundHandlerAdapter;
6 | import io.netty.util.concurrent.Promise;
7 |
8 | public final class DirectClientHandler extends ChannelInboundHandlerAdapter {
9 |
10 | private final Promise promise;
11 |
12 | public DirectClientHandler(Promise promise) {
13 | this.promise = promise;
14 | }
15 |
16 | @Override
17 | public void channelActive(ChannelHandlerContext ctx) {
18 | ctx.pipeline().remove(this);
19 | promise.setSuccess(ctx.channel());
20 | }
21 |
22 | @Override
23 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
24 | promise.setFailure(throwable);
25 | }
26 | }
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/HttpProxyRequestHead.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | public class HttpProxyRequestHead {
6 | private String host;
7 | private int port;
8 | private String proxyType;//tunnel or web
9 | private String protocolVersion;
10 |
11 | private ByteBuf byteBuf;
12 |
13 | public HttpProxyRequestHead(String host, int port, String proxyType, String protocolVersion, ByteBuf byteBuf) {
14 | this.host = host;
15 | this.port = port;
16 | this.proxyType = proxyType;
17 | this.protocolVersion = protocolVersion;
18 | this.byteBuf = byteBuf;
19 | }
20 |
21 | public String getHost() {
22 | return host;
23 | }
24 |
25 | public void setHost(String host) {
26 | this.host = host;
27 | }
28 |
29 | public int getPort() {
30 | return port;
31 | }
32 |
33 | public void setPort(int port) {
34 | this.port = port;
35 | }
36 |
37 | public String getProxyType() {
38 | return proxyType;
39 | }
40 |
41 | public void setProxyType(String proxyType) {
42 | this.proxyType = proxyType;
43 | }
44 |
45 | public ByteBuf getByteBuf() {
46 | return byteBuf;
47 | }
48 |
49 | public void setByteBuf(ByteBuf byteBuf) {
50 | this.byteBuf = byteBuf;
51 | }
52 |
53 | public String getProtocolVersion() {
54 | return protocolVersion;
55 | }
56 |
57 | public void setProtocolVersion(String protocolVersion) {
58 | this.protocolVersion = protocolVersion;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/HttpProxyServerInitializer.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelInitializer;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.handler.logging.LoggingHandler;
7 |
8 | public class HttpProxyServerInitializer extends ChannelInitializer {
9 |
10 | protected void initChannel(Channel channel) throws Exception {
11 | ChannelPipeline p = channel.pipeline();
12 | p.addLast(new LoggingHandler());
13 | p.addLast(new HttpServerHeadDecoder());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/HttpServer.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import io.netty.bootstrap.ServerBootstrap;
4 | import io.netty.channel.*;
5 | import io.netty.channel.nio.NioEventLoopGroup;
6 | import io.netty.channel.socket.nio.NioServerSocketChannel;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | public class HttpServer {
11 |
12 | private Logger logger = LoggerFactory.getLogger(HttpServer.class);
13 |
14 | private ServerBootstrap serverBootstrap;
15 |
16 | private EventLoopGroup serverEventLoopGroup;
17 |
18 | private Channel acceptorChannel;
19 |
20 | public void startServer(){
21 | logger.info("Proxy Server starting...");
22 |
23 | serverEventLoopGroup = new NioEventLoopGroup(4);
24 |
25 | serverBootstrap = new ServerBootstrap()
26 | .channel(NioServerSocketChannel.class)
27 | .childHandler(new HttpProxyServerInitializer())
28 | .group(serverEventLoopGroup);
29 | acceptorChannel = serverBootstrap.bind(17891).syncUninterruptibly().channel();
30 | }
31 |
32 | public void shutdown(){
33 | logger.info("Proxy Server shutting down...");
34 | acceptorChannel.close().syncUninterruptibly();
35 | serverEventLoopGroup.shutdownGracefully().syncUninterruptibly();
36 | logger.info("shutdown completed!");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/HttpServerConnectHandler.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 | * https://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 cc.leevi.common.httpproxy;
17 |
18 | import io.netty.bootstrap.Bootstrap;
19 | import io.netty.buffer.Unpooled;
20 | import io.netty.channel.Channel;
21 | import io.netty.channel.ChannelFuture;
22 | import io.netty.channel.ChannelFutureListener;
23 | import io.netty.channel.ChannelHandler;
24 | import io.netty.channel.ChannelHandlerContext;
25 | import io.netty.channel.ChannelOption;
26 | import io.netty.channel.SimpleChannelInboundHandler;
27 | import io.netty.channel.socket.nio.NioSocketChannel;
28 | import io.netty.util.concurrent.Future;
29 | import io.netty.util.concurrent.FutureListener;
30 | import io.netty.util.concurrent.Promise;
31 |
32 | @ChannelHandler.Sharable
33 | public final class HttpServerConnectHandler extends SimpleChannelInboundHandler {
34 |
35 | private final Bootstrap b = new Bootstrap();
36 |
37 | @Override
38 | public void channelRead0(final ChannelHandlerContext ctx, final HttpProxyRequestHead requestHead) throws Exception {
39 |
40 | Promise promise = ctx.executor().newPromise();
41 | final Channel inboundChannel = ctx.channel();
42 | promise.addListener(
43 | new FutureListener() {
44 | @Override
45 | public void operationComplete(final Future future) throws Exception {
46 | final Channel outboundChannel = future.getNow();
47 | if (future.isSuccess()) {
48 | ChannelFuture responseFuture;
49 | if("TUNNEL".equals(requestHead.getProxyType())){
50 | responseFuture = inboundChannel.writeAndFlush(Unpooled.wrappedBuffer((requestHead.getProtocolVersion() + " 200 Connection Established\r\n\r\n").getBytes()));
51 | }else if("WEB".equals(requestHead.getProxyType())){
52 | responseFuture = outboundChannel.writeAndFlush(requestHead.getByteBuf());
53 | }else{
54 | HttpServerUtils.closeOnFlush(inboundChannel);
55 | return;
56 | }
57 | responseFuture.addListener(new ChannelFutureListener() {
58 | @Override
59 | public void operationComplete(ChannelFuture channelFuture) {
60 | ctx.pipeline().remove(HttpServerConnectHandler.this);
61 | outboundChannel.pipeline().addLast(new RelayHandler(inboundChannel));
62 | ctx.pipeline().addLast(new RelayHandler(outboundChannel));
63 | }
64 | });
65 | } else {
66 | HttpServerUtils.closeOnFlush(inboundChannel);
67 | }
68 | }
69 | });
70 |
71 | b.group(inboundChannel.eventLoop())
72 | .channel(NioSocketChannel.class)
73 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
74 | .option(ChannelOption.SO_KEEPALIVE, true)
75 | .handler(new DirectClientHandler(promise));
76 |
77 | b.connect(requestHead.getHost(), requestHead.getPort()).addListener(new ChannelFutureListener() {
78 | @Override
79 | public void operationComplete(ChannelFuture future) throws Exception {
80 | if (future.isSuccess()) {
81 | // Connection established use handler provided results
82 | } else {
83 | // Close the connection if the connection attempt has failed.
84 | HttpServerUtils.closeOnFlush(inboundChannel);
85 | }
86 | }
87 | });
88 | }
89 |
90 | @Override
91 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
92 | HttpServerUtils.closeOnFlush(ctx.channel());
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/HttpServerHeadDecoder.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import com.google.common.net.HostAndPort;
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.SimpleChannelInboundHandler;
7 | import io.netty.handler.codec.http.HttpConstants;
8 | import io.netty.handler.codec.http.HttpMethod;
9 | import io.netty.util.ByteProcessor;
10 | import io.netty.util.internal.AppendableCharSequence;
11 |
12 | import java.net.URL;
13 |
14 | public class HttpServerHeadDecoder extends SimpleChannelInboundHandler {
15 |
16 | private HttpServerHeadDecoder.HeadLineByteProcessor headLineByteProcessor = new HttpServerHeadDecoder.HeadLineByteProcessor();
17 |
18 | private
19 |
20 | class HeadLineByteProcessor implements ByteProcessor{
21 | private AppendableCharSequence seq;
22 |
23 | public HeadLineByteProcessor() {
24 | this.seq = new AppendableCharSequence(4096);
25 | }
26 |
27 | public AppendableCharSequence parse(ByteBuf buffer) {
28 | seq.reset();
29 | int i = buffer.forEachByte(this);
30 | if (i == -1) {
31 | return null;
32 | }
33 | buffer.readerIndex(i + 1);
34 | return seq;
35 | }
36 |
37 | @Override
38 | public boolean process(byte value) throws Exception {
39 | char nextByte = (char) (value & 0xFF);
40 | if (nextByte == HttpConstants.LF) {
41 | int len = seq.length();
42 | if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
43 | seq.append(nextByte);
44 | }
45 | return false;
46 | }
47 | //continue loop byte
48 | seq.append(nextByte);
49 | return true;
50 | }
51 | }
52 |
53 | @Override
54 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
55 | AppendableCharSequence seq = headLineByteProcessor.parse(in);
56 | if(seq.charAt(seq.length()-1) == HttpConstants.LF){
57 | HttpProxyRequestHead httpProxyRequestHead;
58 | String[] splitInitialLine = splitInitialLine(seq);
59 | String method = splitInitialLine[0];
60 | String uri = splitInitialLine[1];
61 | String protocolVersion = splitInitialLine[2];
62 | String host;
63 | int port;
64 | if(HttpMethod.CONNECT.name().equals(method)){
65 | //https tunnel proxy
66 | HostAndPort hostAndPort = HostAndPort.fromString(uri);
67 | host = hostAndPort.getHost();
68 | port = hostAndPort.getPort();
69 |
70 | httpProxyRequestHead = new HttpProxyRequestHead(host, port, "TUNNEL",protocolVersion,null);
71 | }else{
72 | //http proxy
73 | URL url = new URL(uri);
74 | host = url.getHost();
75 | port = url.getPort();
76 | if(port == -1){
77 | port = 80;
78 | }
79 |
80 | httpProxyRequestHead = new HttpProxyRequestHead(host, port,"WEB",protocolVersion,in.retain().resetReaderIndex());
81 | }
82 | ctx.pipeline().addLast(new HttpServerConnectHandler()).remove(this);
83 | ctx.fireChannelRead(httpProxyRequestHead);
84 | }
85 | }
86 |
87 | private static String[] splitInitialLine(AppendableCharSequence sb) {
88 | int aStart;
89 | int aEnd;
90 | int bStart;
91 | int bEnd;
92 | int cStart;
93 | int cEnd;
94 |
95 | aStart = findNonSPLenient(sb, 0);
96 | aEnd = findSPLenient(sb, aStart);
97 |
98 | bStart = findNonSPLenient(sb, aEnd);
99 | bEnd = findSPLenient(sb, bStart);
100 |
101 | cStart = findNonSPLenient(sb, bEnd);
102 | cEnd = findEndOfString(sb);
103 |
104 | return new String[] {
105 | sb.subStringUnsafe(aStart, aEnd),
106 | sb.subStringUnsafe(bStart, bEnd),
107 | cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
108 | }
109 |
110 | private static int findNonSPLenient(AppendableCharSequence sb, int offset) {
111 | for (int result = offset; result < sb.length(); ++result) {
112 | char c = sb.charAtUnsafe(result);
113 | // See https://tools.ietf.org/html/rfc7230#section-3.5
114 | if (isSPLenient(c)) {
115 | continue;
116 | }
117 | if (Character.isWhitespace(c)) {
118 | // Any other whitespace delimiter is invalid
119 | throw new IllegalArgumentException("Invalid separator");
120 | }
121 | return result;
122 | }
123 | return sb.length();
124 | }
125 |
126 | private static int findSPLenient(AppendableCharSequence sb, int offset) {
127 | for (int result = offset; result < sb.length(); ++result) {
128 | if (isSPLenient(sb.charAtUnsafe(result))) {
129 | return result;
130 | }
131 | }
132 | return sb.length();
133 | }
134 |
135 | private static boolean isSPLenient(char c) {
136 | // See https://tools.ietf.org/html/rfc7230#section-3.5
137 | return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D;
138 | }
139 |
140 | private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) {
141 | for (int result = offset; result < sb.length(); ++result) {
142 | char c = sb.charAtUnsafe(result);
143 | if (!Character.isWhitespace(c)) {
144 | return result;
145 | } else if (validateOWS && !isOWS(c)) {
146 | // Only OWS is supported for whitespace
147 | throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
148 | " but received a '" + c + "'");
149 | }
150 | }
151 | return sb.length();
152 | }
153 |
154 | private static int findEndOfString(AppendableCharSequence sb) {
155 | for (int result = sb.length() - 1; result > 0; --result) {
156 | if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
157 | return result + 1;
158 | }
159 | }
160 | return 0;
161 | }
162 |
163 | private static boolean isOWS(char ch) {
164 | return ch == ' ' || ch == (char) 0x09;
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/HttpServerUtils.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 |
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.Channel;
6 | import io.netty.channel.ChannelFutureListener;
7 |
8 | public final class HttpServerUtils {
9 |
10 | /**
11 | * Closes the specified channel after all queued write requests are flushed.
12 | */
13 | public static void closeOnFlush(Channel ch) {
14 | if (ch.isActive()) {
15 | ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
16 | }
17 | }
18 |
19 | private HttpServerUtils() { }
20 | }
--------------------------------------------------------------------------------
/http-proxy/src/main/java/cc/leevi/common/httpproxy/RelayHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import io.netty.buffer.Unpooled;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelFutureListener;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.ChannelInboundHandlerAdapter;
8 | import io.netty.util.ReferenceCountUtil;
9 |
10 | public class RelayHandler extends ChannelInboundHandlerAdapter {
11 | private final Channel relayChannel;
12 |
13 | public RelayHandler(Channel relayChannel) {
14 | this.relayChannel = relayChannel;
15 | }
16 |
17 | @Override
18 | public void channelActive(ChannelHandlerContext ctx) {
19 | ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
20 | }
21 |
22 | @Override
23 | public void channelRead(ChannelHandlerContext ctx, Object msg) {
24 | if (relayChannel.isActive()) {
25 | relayChannel.writeAndFlush(msg);
26 | } else {
27 | ReferenceCountUtil.release(msg);
28 | }
29 | }
30 |
31 | @Override
32 | public void channelInactive(ChannelHandlerContext ctx) {
33 | HttpServerUtils.closeOnFlush(ctx.channel());
34 | }
35 |
36 | @Override
37 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
38 | ctx.close();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/http-proxy/src/test/java/cc/leevi/common/httpproxy/HttpProxyClientTest.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.httpproxy;
2 |
3 | import com.google.common.net.HostAndPort;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.io.IOException;
8 |
9 | public class HttpProxyClientTest {
10 |
11 | @Before
12 | public void setUp() throws Exception {
13 | }
14 |
15 | @Test
16 | public void startServer() throws IOException {
17 | HttpServer httpServer = new HttpServer();
18 | httpServer.startServer();
19 | System.in.read();
20 | }
21 |
22 | @Test
23 | public void parseURI(){
24 | HostAndPort hostAndPort = HostAndPort.fromString("cdn.segmentfault.com:443");
25 | System.out.println(hostAndPort.getHost());
26 | System.out.println(hostAndPort.getPort());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/http-proxy/src/test/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/mixin-proxy/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 |
8 |
9 | netty-proxy-server
10 | cc.leevi.common
11 | 1.0-SNAPSHOT
12 |
13 |
14 | mixin-proxy
15 |
16 | socks5-proxy
17 |
18 |
19 | UTF-8
20 | 1.7
21 | 1.7
22 |
23 |
24 |
25 |
26 | io.netty
27 | netty-all
28 |
29 |
30 | org.apache.commons
31 | commons-lang3
32 |
33 |
34 | org.slf4j
35 | slf4j-api
36 |
37 |
38 | junit
39 | junit
40 | 4.13.1
41 | test
42 |
43 |
44 | org.apache.logging.log4j
45 | log4j-core
46 | 2.14.0
47 | test
48 |
49 |
50 | org.apache.logging.log4j
51 | log4j-slf4j-impl
52 | 2.14.0
53 | test
54 |
55 |
56 | com.google.guava
57 | guava
58 | 30.0-jre
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/DirectClientHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.channel.ChannelInboundHandlerAdapter;
6 | import io.netty.util.concurrent.Promise;
7 |
8 | public final class DirectClientHandler extends ChannelInboundHandlerAdapter {
9 |
10 | private final Promise promise;
11 |
12 | public DirectClientHandler(Promise promise) {
13 | this.promise = promise;
14 | }
15 |
16 | @Override
17 | public void channelActive(ChannelHandlerContext ctx) {
18 | ctx.pipeline().remove(this);
19 | promise.setSuccess(ctx.channel());
20 | }
21 |
22 | @Override
23 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
24 | promise.setFailure(throwable);
25 | }
26 | }
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/HttpProxyRequestHead.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | public class HttpProxyRequestHead {
6 | private String host;
7 | private int port;
8 | private String proxyType;//tunnel or web
9 | private String protocolVersion;
10 |
11 | private ByteBuf byteBuf;
12 |
13 | public HttpProxyRequestHead(String host, int port, String proxyType, String protocolVersion, ByteBuf byteBuf) {
14 | this.host = host;
15 | this.port = port;
16 | this.proxyType = proxyType;
17 | this.protocolVersion = protocolVersion;
18 | this.byteBuf = byteBuf;
19 | }
20 |
21 | public String getHost() {
22 | return host;
23 | }
24 |
25 | public void setHost(String host) {
26 | this.host = host;
27 | }
28 |
29 | public int getPort() {
30 | return port;
31 | }
32 |
33 | public void setPort(int port) {
34 | this.port = port;
35 | }
36 |
37 | public String getProxyType() {
38 | return proxyType;
39 | }
40 |
41 | public void setProxyType(String proxyType) {
42 | this.proxyType = proxyType;
43 | }
44 |
45 | public ByteBuf getByteBuf() {
46 | return byteBuf;
47 | }
48 |
49 | public void setByteBuf(ByteBuf byteBuf) {
50 | this.byteBuf = byteBuf;
51 | }
52 |
53 | public String getProtocolVersion() {
54 | return protocolVersion;
55 | }
56 |
57 | public void setProtocolVersion(String protocolVersion) {
58 | this.protocolVersion = protocolVersion;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/HttpServerConnectHandler.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 | * https://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 cc.leevi.common.socks5proxy;
17 |
18 | import io.netty.bootstrap.Bootstrap;
19 | import io.netty.buffer.Unpooled;
20 | import io.netty.channel.*;
21 | import io.netty.channel.socket.nio.NioSocketChannel;
22 | import io.netty.util.concurrent.Future;
23 | import io.netty.util.concurrent.FutureListener;
24 | import io.netty.util.concurrent.Promise;
25 |
26 | @ChannelHandler.Sharable
27 | public final class HttpServerConnectHandler extends SimpleChannelInboundHandler {
28 |
29 | private final Bootstrap b = new Bootstrap();
30 |
31 | @Override
32 | public void channelRead0(final ChannelHandlerContext ctx, final HttpProxyRequestHead requestHead) throws Exception {
33 |
34 |
35 | Promise promise = ctx.executor().newPromise();
36 | final Channel inboundChannel = ctx.channel();
37 | promise.addListener(
38 | new FutureListener() {
39 | @Override
40 | public void operationComplete(final Future future) throws Exception {
41 | final Channel outboundChannel = future.getNow();
42 | if (future.isSuccess()) {
43 | ChannelFuture responseFuture;
44 | if("TUNNEL".equals(requestHead.getProxyType())){
45 | responseFuture = inboundChannel.writeAndFlush(Unpooled.wrappedBuffer((requestHead.getProtocolVersion() + " 200 Connection Established\r\n\r\n").getBytes()));
46 | }else if("WEB".equals(requestHead.getProxyType())){
47 | responseFuture = outboundChannel.writeAndFlush(requestHead.getByteBuf());
48 | }else{
49 | MixinServerUtils.closeOnFlush(inboundChannel);
50 | return;
51 | }
52 | responseFuture.addListener(new ChannelFutureListener() {
53 | @Override
54 | public void operationComplete(ChannelFuture channelFuture) {
55 | ctx.pipeline().remove(HttpServerConnectHandler.this);
56 | outboundChannel.pipeline().addLast(new RelayHandler(inboundChannel));
57 | ctx.pipeline().addLast(new RelayHandler(outboundChannel));
58 | }
59 | });
60 | } else {
61 | MixinServerUtils.closeOnFlush(inboundChannel);
62 | }
63 | }
64 | });
65 |
66 | b.group(inboundChannel.eventLoop())
67 | .channel(NioSocketChannel.class)
68 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
69 | .option(ChannelOption.SO_KEEPALIVE, true)
70 | .handler(new DirectClientHandler(promise));
71 |
72 | b.connect(requestHead.getHost(), requestHead.getPort()).addListener(new ChannelFutureListener() {
73 | @Override
74 | public void operationComplete(ChannelFuture future) throws Exception {
75 | if (future.isSuccess()) {
76 | // Connection established use handler provided results
77 | } else {
78 | // Close the connection if the connection attempt has failed.
79 | MixinServerUtils.closeOnFlush(inboundChannel);
80 | }
81 | }
82 | });
83 | }
84 |
85 | @Override
86 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
87 | MixinServerUtils.closeOnFlush(ctx.channel());
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/HttpServerHeadDecoder.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import com.google.common.net.HostAndPort;
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.SimpleChannelInboundHandler;
7 | import io.netty.handler.codec.http.HttpConstants;
8 | import io.netty.handler.codec.http.HttpMethod;
9 | import io.netty.util.ByteProcessor;
10 | import io.netty.util.internal.AppendableCharSequence;
11 |
12 | import java.net.URL;
13 |
14 | public class HttpServerHeadDecoder extends SimpleChannelInboundHandler {
15 |
16 | private HeadLineByteProcessor headLineByteProcessor = new HeadLineByteProcessor();
17 |
18 | private
19 |
20 | class HeadLineByteProcessor implements ByteProcessor{
21 | private AppendableCharSequence seq;
22 |
23 | public HeadLineByteProcessor() {
24 | this.seq = new AppendableCharSequence(4096);
25 | }
26 |
27 | public AppendableCharSequence parse(ByteBuf buffer) {
28 | seq.reset();
29 | int i = buffer.forEachByte(this);
30 | if (i == -1) {
31 | return null;
32 | }
33 | buffer.readerIndex(i + 1);
34 | return seq;
35 | }
36 |
37 | @Override
38 | public boolean process(byte value) throws Exception {
39 | char nextByte = (char) (value & 0xFF);
40 | if (nextByte == HttpConstants.LF) {
41 | int len = seq.length();
42 | if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
43 | seq.append(nextByte);
44 | }
45 | return false;
46 | }
47 | //continue loop byte
48 | seq.append(nextByte);
49 | return true;
50 | }
51 | }
52 |
53 | @Override
54 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
55 | AppendableCharSequence seq = headLineByteProcessor.parse(in);
56 | if(seq.charAt(seq.length()-1) == HttpConstants.LF){
57 | HttpProxyRequestHead httpProxyRequestHead;
58 | String[] splitInitialLine = splitInitialLine(seq);
59 | String method = splitInitialLine[0];
60 | String uri = splitInitialLine[1];
61 | String protocolVersion = splitInitialLine[2];
62 | String host;
63 | int port;
64 | if(HttpMethod.CONNECT.name().equals(method)){
65 | //https tunnel proxy
66 | HostAndPort hostAndPort = HostAndPort.fromString(uri);
67 | host = hostAndPort.getHost();
68 | port = hostAndPort.getPort();
69 |
70 | httpProxyRequestHead = new HttpProxyRequestHead(host, port, "TUNNEL",protocolVersion,null);
71 | }else{
72 | //http proxy
73 | URL url = new URL(uri);
74 | host = url.getHost();
75 | port = url.getPort();
76 | if(port == -1){
77 | port = 80;
78 | }
79 |
80 | httpProxyRequestHead = new HttpProxyRequestHead(host, port, protocolVersion,"WEB",in.resetReaderIndex());
81 | }
82 | ctx.pipeline().addLast(new HttpServerConnectHandler()).remove(this);
83 | ctx.fireChannelRead(httpProxyRequestHead);
84 | }
85 | }
86 |
87 | private static String[] splitInitialLine(AppendableCharSequence sb) {
88 | int aStart;
89 | int aEnd;
90 | int bStart;
91 | int bEnd;
92 | int cStart;
93 | int cEnd;
94 |
95 | aStart = findNonSPLenient(sb, 0);
96 | aEnd = findSPLenient(sb, aStart);
97 |
98 | bStart = findNonSPLenient(sb, aEnd);
99 | bEnd = findSPLenient(sb, bStart);
100 |
101 | cStart = findNonSPLenient(sb, bEnd);
102 | cEnd = findEndOfString(sb);
103 |
104 | return new String[] {
105 | sb.subStringUnsafe(aStart, aEnd),
106 | sb.subStringUnsafe(bStart, bEnd),
107 | cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
108 | }
109 |
110 | private static int findNonSPLenient(AppendableCharSequence sb, int offset) {
111 | for (int result = offset; result < sb.length(); ++result) {
112 | char c = sb.charAtUnsafe(result);
113 | // See https://tools.ietf.org/html/rfc7230#section-3.5
114 | if (isSPLenient(c)) {
115 | continue;
116 | }
117 | if (Character.isWhitespace(c)) {
118 | // Any other whitespace delimiter is invalid
119 | throw new IllegalArgumentException("Invalid separator");
120 | }
121 | return result;
122 | }
123 | return sb.length();
124 | }
125 |
126 | private static int findSPLenient(AppendableCharSequence sb, int offset) {
127 | for (int result = offset; result < sb.length(); ++result) {
128 | if (isSPLenient(sb.charAtUnsafe(result))) {
129 | return result;
130 | }
131 | }
132 | return sb.length();
133 | }
134 |
135 | private static boolean isSPLenient(char c) {
136 | // See https://tools.ietf.org/html/rfc7230#section-3.5
137 | return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D;
138 | }
139 |
140 | private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) {
141 | for (int result = offset; result < sb.length(); ++result) {
142 | char c = sb.charAtUnsafe(result);
143 | if (!Character.isWhitespace(c)) {
144 | return result;
145 | } else if (validateOWS && !isOWS(c)) {
146 | // Only OWS is supported for whitespace
147 | throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
148 | " but received a '" + c + "'");
149 | }
150 | }
151 | return sb.length();
152 | }
153 |
154 | private static int findEndOfString(AppendableCharSequence sb) {
155 | for (int result = sb.length() - 1; result > 0; --result) {
156 | if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
157 | return result + 1;
158 | }
159 | }
160 | return 0;
161 | }
162 |
163 | private static boolean isOWS(char ch) {
164 | return ch == ' ' || ch == (char) 0x09;
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/MixinProxyServer.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.bootstrap.ServerBootstrap;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.EventLoopGroup;
6 | import io.netty.channel.nio.NioEventLoopGroup;
7 | import io.netty.channel.socket.nio.NioServerSocketChannel;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | public class MixinProxyServer {
12 | private Logger logger = LoggerFactory.getLogger(MixinProxyServer.class);
13 |
14 | private ServerBootstrap serverBootstrap;
15 |
16 | private EventLoopGroup serverEventLoopGroup;
17 |
18 | private Channel acceptorChannel;
19 |
20 | public void startServer(){
21 | logger.info("Proxy Server starting...");
22 |
23 | serverEventLoopGroup = new NioEventLoopGroup(4);
24 |
25 | serverBootstrap = new ServerBootstrap()
26 | .channel(NioServerSocketChannel.class)
27 | .childHandler(new MixinServerInitializer())
28 | .group(serverEventLoopGroup);
29 | acceptorChannel = serverBootstrap.bind(8065).syncUninterruptibly().channel();
30 | }
31 |
32 | public void shutdown(){
33 | logger.info("Proxy Server shutting down...");
34 | acceptorChannel.close().syncUninterruptibly();
35 | serverEventLoopGroup.shutdownGracefully().syncUninterruptibly();
36 | logger.info("shutdown completed!");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/MixinSelectHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.channel.SimpleChannelInboundHandler;
7 | import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler;
8 | import io.netty.handler.codec.socksx.SocksVersion;
9 |
10 | public class MixinSelectHandler extends SimpleChannelInboundHandler {
11 |
12 | @Override
13 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
14 | final int readerIndex = msg.readerIndex();
15 | if (msg.writerIndex() == readerIndex) {
16 | return;
17 | }
18 |
19 | ChannelPipeline p = ctx.pipeline();
20 | final byte versionVal = msg.getByte(readerIndex);
21 |
22 | SocksVersion version = SocksVersion.valueOf(versionVal);
23 | if(version.equals(SocksVersion.SOCKS4a) || version.equals(SocksVersion.SOCKS5)){
24 | //socks proxy
25 | p.addLast(new SocksPortUnificationServerHandler(),
26 | SocksServerHandler.INSTANCE).remove(this);
27 | }else{
28 | //http/tunnel proxy
29 | p.addLast(new HttpServerHeadDecoder()).remove(this);
30 | }
31 | msg.retain();
32 | ctx.fireChannelRead(msg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/MixinServerInitializer.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
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 MixinServerInitializer extends ChannelInitializer {
10 | @Override
11 | public void initChannel(SocketChannel ch) throws Exception {
12 | ch.pipeline().addLast(
13 | new LoggingHandler(LogLevel.DEBUG),
14 | new MixinSelectHandler());
15 | }
16 | }
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/MixinServerUtils.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 |
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.Channel;
6 | import io.netty.channel.ChannelFutureListener;
7 |
8 | public final class MixinServerUtils {
9 |
10 | /**
11 | * Closes the specified channel after all queued write requests are flushed.
12 | */
13 | public static void closeOnFlush(Channel ch) {
14 | if (ch.isActive()) {
15 | ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
16 | }
17 | }
18 |
19 | private MixinServerUtils() { }
20 | }
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/RelayHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.buffer.Unpooled;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelFutureListener;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.ChannelInboundHandlerAdapter;
8 | import io.netty.util.ReferenceCountUtil;
9 |
10 | public class RelayHandler extends ChannelInboundHandlerAdapter {
11 | private final Channel relayChannel;
12 |
13 | public RelayHandler(Channel relayChannel) {
14 | this.relayChannel = relayChannel;
15 | }
16 |
17 | @Override
18 | public void channelActive(ChannelHandlerContext ctx) {
19 | ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
20 | }
21 |
22 | @Override
23 | public void channelRead(ChannelHandlerContext ctx, Object msg) {
24 | if (relayChannel.isActive()) {
25 | relayChannel.writeAndFlush(msg);
26 | } else {
27 | ReferenceCountUtil.release(msg);
28 | }
29 | }
30 |
31 | @Override
32 | public void channelInactive(ChannelHandlerContext ctx) {
33 | if (relayChannel.isActive()) {
34 | relayChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
35 | }
36 | }
37 |
38 | @Override
39 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
40 | cause.printStackTrace();
41 | ctx.close();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/SocksServerConnectHandler.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 | * https://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 cc.leevi.common.socks5proxy;
17 |
18 | import io.netty.bootstrap.Bootstrap;
19 | import io.netty.channel.*;
20 | import io.netty.channel.socket.nio.NioSocketChannel;
21 | import io.netty.handler.codec.socksx.SocksMessage;
22 | import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
23 | import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
24 | import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
25 | import io.netty.util.concurrent.Future;
26 | import io.netty.util.concurrent.FutureListener;
27 | import io.netty.util.concurrent.Promise;
28 |
29 | @ChannelHandler.Sharable
30 | public final class SocksServerConnectHandler extends SimpleChannelInboundHandler {
31 |
32 | private final Bootstrap b = new Bootstrap();
33 |
34 | @Override
35 | public void channelRead0(final ChannelHandlerContext ctx, final SocksMessage message) throws Exception {
36 | final Socks5CommandRequest request = (Socks5CommandRequest) message;
37 |
38 | Promise promise = ctx.executor().newPromise();
39 | promise.addListener(
40 | new FutureListener() {
41 | @Override
42 | public void operationComplete(final Future future) throws Exception {
43 | final Channel outboundChannel = future.getNow();
44 | if (future.isSuccess()) {
45 | ChannelFuture responseFuture =
46 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
47 | Socks5CommandStatus.SUCCESS,
48 | request.dstAddrType(),
49 | request.dstAddr(),
50 | request.dstPort()));
51 |
52 | responseFuture.addListener(new ChannelFutureListener() {
53 | @Override
54 | public void operationComplete(ChannelFuture channelFuture) {
55 | ctx.pipeline().remove(SocksServerConnectHandler.this);
56 | outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel()));
57 | ctx.pipeline().addLast(new RelayHandler(outboundChannel));
58 | }
59 | });
60 | } else {
61 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
62 | Socks5CommandStatus.FAILURE, request.dstAddrType()));
63 | MixinServerUtils.closeOnFlush(ctx.channel());
64 | }
65 | }
66 | });
67 |
68 | final Channel inboundChannel = ctx.channel();
69 | b.group(inboundChannel.eventLoop())
70 | .channel(NioSocketChannel.class)
71 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
72 | .option(ChannelOption.SO_KEEPALIVE, true)
73 | .handler(new DirectClientHandler(promise));
74 |
75 | b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() {
76 | @Override
77 | public void operationComplete(ChannelFuture future) throws Exception {
78 | if (future.isSuccess()) {
79 | // Connection established use handler provided results
80 | } else {
81 | // Close the connection if the connection attempt has failed.
82 | ctx.channel().writeAndFlush(
83 | new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, request.dstAddrType()));
84 | MixinServerUtils.closeOnFlush(ctx.channel());
85 | }
86 | }
87 | });
88 | }
89 |
90 | @Override
91 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
92 | MixinServerUtils.closeOnFlush(ctx.channel());
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/mixin-proxy/src/main/java/cc/leevi/common/socks5proxy/SocksServerHandler.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 | * https://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 cc.leevi.common.socks5proxy;
17 |
18 | import io.netty.buffer.Unpooled;
19 | import io.netty.channel.ChannelHandler;
20 | import io.netty.channel.ChannelHandlerContext;
21 | import io.netty.channel.SimpleChannelInboundHandler;
22 | import io.netty.handler.codec.socksx.SocksMessage;
23 | import io.netty.handler.codec.socksx.SocksVersion;
24 | import io.netty.handler.codec.socksx.v5.*;
25 | import org.slf4j.Logger;
26 | import org.slf4j.LoggerFactory;
27 |
28 | @ChannelHandler.Sharable
29 | public final class SocksServerHandler extends SimpleChannelInboundHandler {
30 |
31 | private Logger logger = LoggerFactory.getLogger(SocksServerHandler.class);
32 |
33 | public static final SocksServerHandler INSTANCE = new SocksServerHandler();
34 |
35 | private SocksServerHandler() { }
36 |
37 | @Override
38 | public void channelRead0(ChannelHandlerContext ctx, SocksMessage socksRequest) throws Exception {
39 | if(!socksRequest.version().equals(SocksVersion.SOCKS5)){
40 | logger.error("only supports socks5 protocol!");
41 | ctx.writeAndFlush(Unpooled.wrappedBuffer("protocol version illegal!".getBytes()));
42 | return ;
43 | }
44 | if (socksRequest instanceof Socks5InitialRequest) {
45 | ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
46 | ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH));
47 | //如果需要密码,这里可以换成
48 | // ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.PASSWORD));
49 | } else if (socksRequest instanceof Socks5PasswordAuthRequest) {
50 | //如果需要密码,这里需要验证密码
51 | ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
52 | ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.SUCCESS));
53 | } else if (socksRequest instanceof Socks5CommandRequest) {
54 | Socks5CommandRequest socks5CmdRequest = (Socks5CommandRequest) socksRequest;
55 | if (socks5CmdRequest.type() == Socks5CommandType.CONNECT) {
56 | ctx.pipeline().addLast(new SocksServerConnectHandler());
57 | ctx.pipeline().remove(this);
58 | ctx.fireChannelRead(socksRequest);
59 | } else {
60 | ctx.close();
61 | }
62 | } else {
63 | ctx.close();
64 | }
65 | }
66 |
67 | @Override
68 | public void channelReadComplete(ChannelHandlerContext ctx) {
69 | ctx.flush();
70 | }
71 |
72 | @Override
73 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
74 | logger.error("exceptionCaught",throwable);
75 | MixinServerUtils.closeOnFlush(ctx.channel());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/mixin-proxy/src/test/java/cc/leevi/common/socks5proxy/MixinProxyServerTest.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.io.IOException;
7 |
8 | public class MixinProxyServerTest {
9 |
10 | @Before
11 | public void setUp() throws Exception {
12 | }
13 |
14 | @Test
15 | public void startServer() throws IOException {
16 | MixinProxyServer mixinProxyServer = new MixinProxyServer();
17 | mixinProxyServer.startServer();
18 | System.in.read();
19 | }
20 | }
--------------------------------------------------------------------------------
/mixin-proxy/src/test/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cc.leevi.common
8 | netty-proxy-server
9 | pom
10 | 1.0-SNAPSHOT
11 |
12 | http-proxy
13 | socks5-proxy
14 | mixin-proxy
15 |
16 |
17 |
18 |
19 |
20 | io.netty
21 | netty-all
22 | 4.1.54.Final
23 |
24 |
25 | org.apache.commons
26 | commons-lang3
27 | 3.11
28 |
29 |
30 | org.slf4j
31 | slf4j-api
32 | 1.7.30
33 |
34 |
35 | org.apache.logging.log4j
36 | log4j-core
37 | 2.14.0
38 |
39 |
40 | org.apache.logging.log4j
41 | log4j-slf4j-impl
42 | 2.14.0
43 | test
44 |
45 |
46 | junit
47 | junit
48 | 4.13.1
49 | test
50 |
51 |
52 | com.google.guava
53 | guava
54 | 30.0-jre
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/socks5-proxy/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 |
8 |
9 | netty-proxy-server
10 | cc.leevi.common
11 | 1.0-SNAPSHOT
12 |
13 |
14 | socks5-proxy
15 |
16 | socks5-proxy
17 |
18 |
19 | UTF-8
20 | 1.7
21 | 1.7
22 |
23 |
24 |
25 |
26 | io.netty
27 | netty-all
28 |
29 |
30 | org.apache.commons
31 | commons-lang3
32 |
33 |
34 | org.slf4j
35 | slf4j-api
36 |
37 |
38 | junit
39 | junit
40 | 4.13.1
41 | test
42 |
43 |
44 | org.apache.logging.log4j
45 | log4j-core
46 | 2.14.0
47 | test
48 |
49 |
50 | org.apache.logging.log4j
51 | log4j-slf4j-impl
52 | 2.14.0
53 | test
54 |
55 |
56 | com.google.guava
57 | guava
58 | 30.0-jre
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/DirectClientHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.channel.ChannelInboundHandlerAdapter;
6 | import io.netty.util.concurrent.Promise;
7 |
8 | public final class DirectClientHandler extends ChannelInboundHandlerAdapter {
9 |
10 | private final Promise promise;
11 |
12 | public DirectClientHandler(Promise promise) {
13 | this.promise = promise;
14 | }
15 |
16 | @Override
17 | public void channelActive(ChannelHandlerContext ctx) {
18 | ctx.pipeline().remove(this);
19 | promise.setSuccess(ctx.channel());
20 | }
21 |
22 | @Override
23 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
24 | promise.setFailure(throwable);
25 | }
26 | }
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/RelayHandler.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.buffer.Unpooled;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.ChannelFutureListener;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.channel.ChannelInboundHandlerAdapter;
8 | import io.netty.util.ReferenceCountUtil;
9 |
10 | public class RelayHandler extends ChannelInboundHandlerAdapter {
11 | private final Channel relayChannel;
12 |
13 | public RelayHandler(Channel relayChannel) {
14 | this.relayChannel = relayChannel;
15 | }
16 |
17 | @Override
18 | public void channelActive(ChannelHandlerContext ctx) {
19 | ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
20 | }
21 |
22 | @Override
23 | public void channelRead(ChannelHandlerContext ctx, Object msg) {
24 | if (relayChannel.isActive()) {
25 | relayChannel.writeAndFlush(msg);
26 | } else {
27 | ReferenceCountUtil.release(msg);
28 | }
29 | }
30 |
31 | @Override
32 | public void channelInactive(ChannelHandlerContext ctx) {
33 | if (relayChannel.isActive()) {
34 | relayChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
35 | }
36 | }
37 |
38 | @Override
39 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
40 | cause.printStackTrace();
41 | ctx.close();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/Socks5ProxyServer.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import io.netty.bootstrap.ServerBootstrap;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.EventLoopGroup;
6 | import io.netty.channel.nio.NioEventLoopGroup;
7 | import io.netty.channel.socket.nio.NioServerSocketChannel;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | public class Socks5ProxyServer {
12 | private Logger logger = LoggerFactory.getLogger(Socks5ProxyServer.class);
13 |
14 | private ServerBootstrap serverBootstrap;
15 |
16 | private EventLoopGroup serverEventLoopGroup;
17 |
18 | private Channel acceptorChannel;
19 |
20 | public void startServer(){
21 | logger.info("Proxy Server starting...");
22 |
23 | serverEventLoopGroup = new NioEventLoopGroup(4);
24 |
25 | serverBootstrap = new ServerBootstrap()
26 | .channel(NioServerSocketChannel.class)
27 | .childHandler(new SocksServerInitializer())
28 | .group(serverEventLoopGroup);
29 | acceptorChannel = serverBootstrap.bind(1080).syncUninterruptibly().channel();
30 | }
31 |
32 | public void shutdown(){
33 | logger.info("Proxy Server shutting down...");
34 | acceptorChannel.close().syncUninterruptibly();
35 | serverEventLoopGroup.shutdownGracefully().syncUninterruptibly();
36 | logger.info("shutdown completed!");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/SocksServerConnectHandler.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 | * https://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 cc.leevi.common.socks5proxy;
17 |
18 | import io.netty.bootstrap.Bootstrap;
19 | import io.netty.buffer.Unpooled;
20 | import io.netty.channel.Channel;
21 | import io.netty.channel.ChannelFuture;
22 | import io.netty.channel.ChannelFutureListener;
23 | import io.netty.channel.ChannelHandler;
24 | import io.netty.channel.ChannelHandlerContext;
25 | import io.netty.channel.ChannelOption;
26 | import io.netty.channel.SimpleChannelInboundHandler;
27 | import io.netty.channel.socket.nio.NioSocketChannel;
28 | import io.netty.handler.codec.socksx.SocksMessage;
29 | import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandResponse;
30 | import io.netty.handler.codec.socksx.v4.Socks4CommandRequest;
31 | import io.netty.handler.codec.socksx.v4.Socks4CommandStatus;
32 | import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
33 | import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
34 | import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
35 | import io.netty.util.concurrent.Future;
36 | import io.netty.util.concurrent.FutureListener;
37 | import io.netty.util.concurrent.Promise;
38 |
39 | @ChannelHandler.Sharable
40 | public final class SocksServerConnectHandler extends SimpleChannelInboundHandler {
41 |
42 | private final Bootstrap b = new Bootstrap();
43 |
44 | @Override
45 | public void channelRead0(final ChannelHandlerContext ctx, final SocksMessage message) throws Exception {
46 | final Socks5CommandRequest request = (Socks5CommandRequest) message;
47 |
48 | Promise promise = ctx.executor().newPromise();
49 | promise.addListener(
50 | new FutureListener() {
51 | @Override
52 | public void operationComplete(final Future future) throws Exception {
53 | final Channel outboundChannel = future.getNow();
54 | if (future.isSuccess()) {
55 | ChannelFuture responseFuture =
56 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
57 | Socks5CommandStatus.SUCCESS,
58 | request.dstAddrType(),
59 | request.dstAddr(),
60 | request.dstPort()));
61 |
62 | responseFuture.addListener(new ChannelFutureListener() {
63 | @Override
64 | public void operationComplete(ChannelFuture channelFuture) {
65 | ctx.pipeline().remove(SocksServerConnectHandler.this);
66 | outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel()));
67 | ctx.pipeline().addLast(new RelayHandler(outboundChannel));
68 | }
69 | });
70 | } else {
71 | ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
72 | Socks5CommandStatus.FAILURE, request.dstAddrType()));
73 | SocksServerUtils.closeOnFlush(ctx.channel());
74 | }
75 | }
76 | });
77 |
78 | final Channel inboundChannel = ctx.channel();
79 | b.group(inboundChannel.eventLoop())
80 | .channel(NioSocketChannel.class)
81 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
82 | .option(ChannelOption.SO_KEEPALIVE, true)
83 | .handler(new DirectClientHandler(promise));
84 |
85 | b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() {
86 | @Override
87 | public void operationComplete(ChannelFuture future) throws Exception {
88 | if (future.isSuccess()) {
89 | // Connection established use handler provided results
90 | } else {
91 | // Close the connection if the connection attempt has failed.
92 | ctx.channel().writeAndFlush(
93 | new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, request.dstAddrType()));
94 | SocksServerUtils.closeOnFlush(ctx.channel());
95 | }
96 | }
97 | });
98 | }
99 |
100 | @Override
101 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
102 | SocksServerUtils.closeOnFlush(ctx.channel());
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/SocksServerHandler.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 | * https://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 cc.leevi.common.socks5proxy;
17 |
18 | import io.netty.buffer.Unpooled;
19 | import io.netty.channel.ChannelHandler;
20 | import io.netty.channel.ChannelHandlerContext;
21 | import io.netty.channel.SimpleChannelInboundHandler;
22 | import io.netty.handler.codec.socksx.SocksMessage;
23 | import io.netty.handler.codec.socksx.SocksVersion;
24 | import io.netty.handler.codec.socksx.v4.Socks4CommandRequest;
25 | import io.netty.handler.codec.socksx.v4.Socks4CommandType;
26 | import io.netty.handler.codec.socksx.v5.*;
27 | import org.slf4j.Logger;
28 | import org.slf4j.LoggerFactory;
29 |
30 | @ChannelHandler.Sharable
31 | public final class SocksServerHandler extends SimpleChannelInboundHandler {
32 |
33 | private Logger logger = LoggerFactory.getLogger(SocksServerHandler.class);
34 |
35 | public static final SocksServerHandler INSTANCE = new SocksServerHandler();
36 |
37 | private SocksServerHandler() { }
38 |
39 | @Override
40 | public void channelRead0(ChannelHandlerContext ctx, SocksMessage socksRequest) throws Exception {
41 | if(!socksRequest.version().equals(SocksVersion.SOCKS5)){
42 | logger.error("only supports socks5 protocol!");
43 | ctx.writeAndFlush(Unpooled.wrappedBuffer("protocol version illegal!".getBytes()));
44 | return ;
45 | }
46 | if (socksRequest instanceof Socks5InitialRequest) {
47 | ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
48 | ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH));
49 | //如果需要密码,这里可以换成
50 | // ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.PASSWORD));
51 | } else if (socksRequest instanceof Socks5PasswordAuthRequest) {
52 | //如果需要密码,这里需要验证密码
53 | ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
54 | ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.SUCCESS));
55 | } else if (socksRequest instanceof Socks5CommandRequest) {
56 | Socks5CommandRequest socks5CmdRequest = (Socks5CommandRequest) socksRequest;
57 | if (socks5CmdRequest.type() == Socks5CommandType.CONNECT) {
58 | ctx.pipeline().addLast(new SocksServerConnectHandler());
59 | ctx.pipeline().remove(this);
60 | ctx.fireChannelRead(socksRequest);
61 | } else {
62 | ctx.close();
63 | }
64 | } else {
65 | ctx.close();
66 | }
67 | }
68 |
69 | @Override
70 | public void channelReadComplete(ChannelHandlerContext ctx) {
71 | ctx.flush();
72 | }
73 |
74 | @Override
75 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
76 | logger.error("exceptionCaught",throwable);
77 | SocksServerUtils.closeOnFlush(ctx.channel());
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/SocksServerInitializer.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
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.DEBUG),
14 | new SocksPortUnificationServerHandler(),
15 | SocksServerHandler.INSTANCE);
16 | }
17 | }
--------------------------------------------------------------------------------
/socks5-proxy/src/main/java/cc/leevi/common/socks5proxy/SocksServerUtils.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 |
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.Channel;
6 | import io.netty.channel.ChannelFutureListener;
7 |
8 | public final class SocksServerUtils {
9 |
10 | /**
11 | * Closes the specified channel after all queued write requests are flushed.
12 | */
13 | public static void closeOnFlush(Channel ch) {
14 | if (ch.isActive()) {
15 | ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
16 | }
17 | }
18 |
19 | private SocksServerUtils() { }
20 | }
--------------------------------------------------------------------------------
/socks5-proxy/src/test/java/cc/leevi/common/socks5proxy/Socks5ProxyServerTest.java:
--------------------------------------------------------------------------------
1 | package cc.leevi.common.socks5proxy;
2 |
3 | import com.google.common.net.HostAndPort;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.io.IOException;
8 |
9 | import static org.junit.Assert.*;
10 |
11 | public class Socks5ProxyServerTest {
12 |
13 | @Before
14 | public void setUp() throws Exception {
15 | }
16 |
17 | @Test
18 | public void startServer() throws IOException {
19 | Socks5ProxyServer socks5ProxyServer = new Socks5ProxyServer();
20 | socks5ProxyServer.startServer();
21 | System.in.read();
22 | }
23 | }
--------------------------------------------------------------------------------
/socks5-proxy/src/test/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------