├── .gitignore
├── natx-common
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── xxg
│ │ └── natx
│ │ └── common
│ │ ├── exception
│ │ └── NatxException.java
│ │ ├── protocol
│ │ ├── NatxMessage.java
│ │ └── NatxMessageType.java
│ │ ├── codec
│ │ ├── NatxMessageDecoder.java
│ │ └── NatxMessageEncoder.java
│ │ └── handler
│ │ └── NatxCommonHandler.java
└── pom.xml
├── natx-server
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── xxg
│ │ └── natx
│ │ └── server
│ │ ├── NatxServerStarter.java
│ │ ├── NatxServer.java
│ │ ├── net
│ │ └── TcpServer.java
│ │ └── handler
│ │ ├── RemoteProxyHandler.java
│ │ └── NatxServerHandler.java
└── pom.xml
├── natx-client
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── xxg
│ │ └── natx
│ │ └── client
│ │ ├── net
│ │ └── TcpConnection.java
│ │ ├── handler
│ │ ├── LocalProxyHandler.java
│ │ └── NatxClientHandler.java
│ │ ├── NatxClient.java
│ │ └── NatxClientStarter.java
└── pom.xml
├── README.md
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | *.iml
3 | **/target
4 | dependency-reduced-pom.xml
--------------------------------------------------------------------------------
/natx-common/src/main/java/com/xxg/natx/common/exception/NatxException.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.common.exception;
2 |
3 | /**
4 | * Created by wucao on 2019/3/2.
5 | */
6 | public class NatxException extends Exception {
7 |
8 | public NatxException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/natx-common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | natx
8 | com.xxg
9 | 1.0.3
10 |
11 | 4.0.0
12 | natx-common
13 |
14 |
15 |
--------------------------------------------------------------------------------
/natx-common/src/main/java/com/xxg/natx/common/protocol/NatxMessage.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.common.protocol;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Created by wucao on 2019/3/2.
7 | */
8 | public class NatxMessage {
9 |
10 | private NatxMessageType type;
11 | private Map metaData;
12 | private byte[] data;
13 |
14 | public NatxMessageType getType() {
15 | return type;
16 | }
17 |
18 | public void setType(NatxMessageType type) {
19 | this.type = type;
20 | }
21 |
22 | public Map getMetaData() {
23 | return metaData;
24 | }
25 |
26 | public void setMetaData(Map metaData) {
27 | this.metaData = metaData;
28 | }
29 |
30 | public byte[] getData() {
31 | return data;
32 | }
33 |
34 | public void setData(byte[] data) {
35 | this.data = data;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/natx-common/src/main/java/com/xxg/natx/common/protocol/NatxMessageType.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.common.protocol;
2 |
3 | import com.xxg.natx.common.exception.NatxException;
4 |
5 | /**
6 | * Created by wucao on 2019/3/2.
7 | */
8 | public enum NatxMessageType {
9 |
10 | REGISTER(1),
11 | REGISTER_RESULT(2),
12 | CONNECTED(3),
13 | DISCONNECTED(4),
14 | DATA(5),
15 | KEEPALIVE(6);
16 |
17 | private int code;
18 |
19 | NatxMessageType(int code) {
20 | this.code = code;
21 | }
22 |
23 | public int getCode() {
24 | return code;
25 | }
26 |
27 | public static NatxMessageType valueOf(int code) throws NatxException {
28 | for (NatxMessageType item : NatxMessageType.values()) {
29 | if (item.code == code) {
30 | return item;
31 | }
32 | }
33 | throw new NatxException("NatxMessageType code error: " + code);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/natx-server/src/main/java/com/xxg/natx/server/NatxServerStarter.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.server;
2 |
3 | import org.apache.commons.cli.*;
4 |
5 | /**
6 | * Created by wucao on 2019/10/23.
7 | */
8 | public class NatxServerStarter {
9 |
10 | public static void main(String[] args) throws ParseException, InterruptedException {
11 |
12 | // args
13 | Options options = new Options();
14 | options.addOption("h", false, "Help");
15 | options.addOption("port", true, "Natx server port");
16 | options.addOption("password", true, "Natx server password");
17 |
18 | CommandLineParser parser = new DefaultParser();
19 | CommandLine cmd = parser.parse(options, args);
20 |
21 | if (cmd.hasOption("h")) {
22 | // print help
23 | HelpFormatter formatter = new HelpFormatter();
24 | formatter.printHelp("options", options);
25 | } else {
26 |
27 | int port = Integer.parseInt(cmd.getOptionValue("port", "7731"));
28 | String password = cmd.getOptionValue("password");
29 | NatxServer server = new NatxServer();
30 | server.start(port, password);
31 |
32 | System.out.println("Natx server started on port " + port);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/natx-client/src/main/java/com/xxg/natx/client/net/TcpConnection.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.client.net;
2 |
3 | import io.netty.bootstrap.Bootstrap;
4 | import io.netty.channel.*;
5 | import io.netty.channel.nio.NioEventLoopGroup;
6 | import io.netty.channel.socket.nio.NioSocketChannel;
7 |
8 | import java.io.IOException;
9 |
10 | /**
11 | * A TCP connection to server
12 | */
13 | public class TcpConnection {
14 |
15 | /**
16 | *
17 | * @param host
18 | * @param port
19 | * @param channelInitializer
20 | * @throws InterruptedException
21 | */
22 | public ChannelFuture connect(String host, int port, ChannelInitializer channelInitializer) throws InterruptedException, IOException {
23 |
24 | NioEventLoopGroup workerGroup = new NioEventLoopGroup();
25 |
26 | try {
27 | Bootstrap b = new Bootstrap();
28 | b.group(workerGroup);
29 | b.channel(NioSocketChannel.class);
30 | b.option(ChannelOption.SO_KEEPALIVE, true);
31 | b.handler(channelInitializer);
32 |
33 | Channel channel = b.connect(host, port).sync().channel();
34 | return channel.closeFuture().addListener(future -> workerGroup.shutdownGracefully());
35 | } catch (Exception e) {
36 | workerGroup.shutdownGracefully();
37 | throw e;
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/natx-server/src/main/java/com/xxg/natx/server/NatxServer.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.server;
2 |
3 | import com.xxg.natx.common.codec.NatxMessageDecoder;
4 | import com.xxg.natx.common.codec.NatxMessageEncoder;
5 | import com.xxg.natx.server.handler.NatxServerHandler;
6 | import com.xxg.natx.server.net.TcpServer;
7 | import io.netty.channel.ChannelInitializer;
8 | import io.netty.channel.socket.SocketChannel;
9 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
10 | import io.netty.handler.timeout.IdleStateHandler;
11 |
12 | /**
13 | * Created by wucao on 2019/2/27.
14 | */
15 | public class NatxServer {
16 |
17 | public void start(int port, String password) throws InterruptedException {
18 |
19 | TcpServer natxClientServer = new TcpServer();
20 | natxClientServer.bind(port, new ChannelInitializer() {
21 | @Override
22 | public void initChannel(SocketChannel ch)
23 | throws Exception {
24 | NatxServerHandler natxServerHandler = new NatxServerHandler(password);
25 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4),
26 | new NatxMessageDecoder(), new NatxMessageEncoder(),
27 | new IdleStateHandler(60, 30, 0), natxServerHandler);
28 | }
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Natx
2 | Natx 是一个基于 Java Netty 实现的可用于内网穿透的代理工具,支持 TCP 协议(如 HTTP 协议)。
3 |
4 | Natx 包含服务端和客户端两部分,服务端运行在带有公网 IP 的服务器上,客户端运行在没有公网 IP 的机器上。
5 |
6 | 由于大部分联网设备只有内网 IP ,例如大部分家庭宽带,我们在本地启动的网络应用无法对外提供访问,这种场景下可以使用 Natx 将本地网络地址映射到外网,对外提供访问。
7 |
8 | 下载: https://github.com/wucao/natx/releases/tag/v1.0.2
9 |
10 | ## 启动
11 | ### 服务端启动
12 | 在带有公网 IP 的服务器上执行 Java 命令:
13 | ```
14 | java -jar natx-server.jar
15 | ```
16 | 看到输出表示启动成功:
17 | ```
18 | Natx server started on port 7731
19 | ```
20 | 默认服务端端口号是7731。注意这个端口号是 Natx 客户端连接 Natx 服务器的端口号,并非对外网提供访问的端口号。
21 |
22 | 指定端口号和 password :
23 | ```
24 | java -jar natx-server.jar -port 9000 -password password123
25 | ```
26 | 默认情况下 Natx 服务端未指定 password ,任何客户端都可以直接连接并使用 Natx 服务器,这样很不安全,建议使用 Natx 服务端时指定一个 password 作为连接服务端的密码。
27 |
28 | ### 客户端启动
29 | 在没有公网 IP 的机器上执行 Java 命令:
30 | ```
31 | java -jar natx-client.jar -server_addr 211.161.xxx.xxx -server_port 7731 -password password123 -proxy_addr localhost -proxy_port 8080 -remote_port 10000
32 | ```
33 |
34 | 参数说明:
35 | - `server_addr` Natx 服务端的网络地址,即 Natx 服务端运行的服务器外网 IP 或 hostname
36 | - `server_port` Natx 服务端的端口
37 | - `password` Natx 服务端的 password
38 | - `proxy_addr` 被代理的应用网络地址
39 | - `proxy_port` 被代理的应用端口号
40 | - `remote_port` Natx 服务端对外访问该应用的端口
41 |
42 | 启动成功后可以通过 server_addr:remote_port 访问被代理的应用,如果被代理的应用是 HTTP 应用,可以通过 http://211.161.xxx.xxx:10000 在外网访问。
43 |
44 | ## 典型使用场景
45 | - 在开发微信公众号服务时,由于本机没有外网 IP ,微信的服务器无法访问到本机接口,调试很不方便,可以使用 Natx 将本地网络地址映射到外网,便于调试。
46 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 | com.xxg
8 | natx
9 | pom
10 | 1.0.3
11 |
12 |
13 | natx-server
14 | natx-client
15 | natx-common
16 |
17 |
18 |
19 | UTF-8
20 | 1.8
21 | 1.8
22 |
23 |
24 |
25 |
26 |
27 | io.netty
28 | netty-all
29 | 4.1.42.Final
30 |
31 |
32 |
33 | commons-cli
34 | commons-cli
35 | 1.4
36 |
37 |
38 |
39 | org.json
40 | json
41 | 20180813
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/natx-server/src/main/java/com/xxg/natx/server/net/TcpServer.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.server.net;
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 |
8 | /**
9 | * Created by wucao on 2019/2/27.
10 | */
11 | public class TcpServer {
12 |
13 | private Channel channel;
14 |
15 | public synchronized void bind(int port, ChannelInitializer channelInitializer) throws InterruptedException {
16 |
17 | EventLoopGroup bossGroup = new NioEventLoopGroup();
18 | EventLoopGroup workerGroup = new NioEventLoopGroup();
19 |
20 | try {
21 | ServerBootstrap b = new ServerBootstrap();
22 | b.group(bossGroup, workerGroup)
23 | .channel(NioServerSocketChannel.class)
24 | .childHandler(channelInitializer)
25 | .childOption(ChannelOption.SO_KEEPALIVE, true);
26 | channel = b.bind(port).sync().channel();
27 | channel.closeFuture().addListener((ChannelFutureListener) future -> {
28 | workerGroup.shutdownGracefully();
29 | bossGroup.shutdownGracefully();
30 | });
31 | } catch (Exception e) {
32 | workerGroup.shutdownGracefully();
33 | bossGroup.shutdownGracefully();
34 | throw e;
35 | }
36 | }
37 |
38 | public synchronized void close() {
39 | if (channel != null) {
40 | channel.close();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/natx-common/src/main/java/com/xxg/natx/common/codec/NatxMessageDecoder.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.common.codec;
2 |
3 | import com.xxg.natx.common.protocol.NatxMessage;
4 | import com.xxg.natx.common.protocol.NatxMessageType;
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.ByteBufUtil;
7 | import io.netty.channel.ChannelHandlerContext;
8 | import io.netty.handler.codec.MessageToMessageDecoder;
9 | import io.netty.util.CharsetUtil;
10 | import org.json.JSONObject;
11 |
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | /**
16 | * Created by wucao on 2019/3/2.
17 | */
18 | public class NatxMessageDecoder extends MessageToMessageDecoder {
19 |
20 | @Override
21 | protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception {
22 |
23 | int type = msg.readInt();
24 | NatxMessageType natxMessageType = NatxMessageType.valueOf(type);
25 |
26 | int metaDataLength = msg.readInt();
27 | CharSequence metaDataString = msg.readCharSequence(metaDataLength, CharsetUtil.UTF_8);
28 | JSONObject jsonObject = new JSONObject(metaDataString.toString());
29 | Map metaData = jsonObject.toMap();
30 |
31 | byte[] data = null;
32 | if (msg.isReadable()) {
33 | data = ByteBufUtil.getBytes(msg);
34 | }
35 |
36 | NatxMessage natxMessage = new NatxMessage();
37 | natxMessage.setType(natxMessageType);
38 | natxMessage.setMetaData(metaData);
39 | natxMessage.setData(data);
40 |
41 | out.add(natxMessage);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/natx-common/src/main/java/com/xxg/natx/common/handler/NatxCommonHandler.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.common.handler;
2 |
3 | import com.xxg.natx.common.protocol.NatxMessage;
4 | import com.xxg.natx.common.protocol.NatxMessageType;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.ChannelInboundHandlerAdapter;
7 | import io.netty.handler.timeout.IdleState;
8 | import io.netty.handler.timeout.IdleStateEvent;
9 |
10 | /**
11 | * Created by wucao on 2019/2/28.
12 | */
13 | public class NatxCommonHandler extends ChannelInboundHandlerAdapter {
14 |
15 | protected ChannelHandlerContext ctx;
16 |
17 | public ChannelHandlerContext getCtx() {
18 | return ctx;
19 | }
20 |
21 | @Override
22 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
23 | this.ctx = ctx;
24 | }
25 |
26 | @Override
27 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
28 | System.out.println("Exception caught ...");
29 | cause.printStackTrace();
30 | }
31 |
32 | @Override
33 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
34 | if (evt instanceof IdleStateEvent) {
35 | IdleStateEvent e = (IdleStateEvent) evt;
36 | if (e.state() == IdleState.READER_IDLE) {
37 | System.out.println("Read idle loss connection.");
38 | ctx.close();
39 | } else if (e.state() == IdleState.WRITER_IDLE) {
40 | NatxMessage natxMessage = new NatxMessage();
41 | natxMessage.setType(NatxMessageType.KEEPALIVE);
42 | ctx.writeAndFlush(natxMessage);
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/natx-common/src/main/java/com/xxg/natx/common/codec/NatxMessageEncoder.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.common.codec;
2 |
3 | import com.xxg.natx.common.protocol.NatxMessage;
4 | import com.xxg.natx.common.protocol.NatxMessageType;
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.handler.codec.MessageToByteEncoder;
8 | import io.netty.util.CharsetUtil;
9 | import org.json.JSONObject;
10 |
11 | import java.io.ByteArrayOutputStream;
12 | import java.io.DataOutputStream;
13 |
14 | /**
15 | * Created by wucao on 2019/3/2.
16 | */
17 | public class NatxMessageEncoder extends MessageToByteEncoder {
18 |
19 | @Override
20 | protected void encode(ChannelHandlerContext ctx, NatxMessage msg, ByteBuf out) throws Exception {
21 |
22 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
23 | try (DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream)) {
24 |
25 | NatxMessageType natxMessageType = msg.getType();
26 | dataOutputStream.writeInt(natxMessageType.getCode());
27 |
28 | JSONObject metaDataJson = new JSONObject(msg.getMetaData());
29 | byte[] metaDataBytes = metaDataJson.toString().getBytes(CharsetUtil.UTF_8);
30 | dataOutputStream.writeInt(metaDataBytes.length);
31 | dataOutputStream.write(metaDataBytes);
32 |
33 | if (msg.getData() != null && msg.getData().length > 0) {
34 | dataOutputStream.write(msg.getData());
35 | }
36 |
37 | byte[] data = byteArrayOutputStream.toByteArray();
38 | out.writeInt(data.length);
39 | out.writeBytes(data);
40 | }
41 |
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/natx-client/src/main/java/com/xxg/natx/client/handler/LocalProxyHandler.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.client.handler;
2 |
3 | import com.xxg.natx.common.handler.NatxCommonHandler;
4 | import com.xxg.natx.common.protocol.NatxMessage;
5 | import com.xxg.natx.common.protocol.NatxMessageType;
6 | import io.netty.channel.ChannelHandlerContext;
7 |
8 | import java.util.HashMap;
9 |
10 | /**
11 | * Created by wucao on 2019/3/2.
12 | */
13 | public class LocalProxyHandler extends NatxCommonHandler {
14 |
15 | private NatxCommonHandler proxyHandler;
16 | private String remoteChannelId;
17 |
18 | public LocalProxyHandler(NatxCommonHandler proxyHandler, String remoteChannelId) {
19 | this.proxyHandler = proxyHandler;
20 | this.remoteChannelId = remoteChannelId;
21 | }
22 |
23 | @Override
24 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
25 | byte[] data = (byte[]) msg;
26 | NatxMessage message = new NatxMessage();
27 | message.setType(NatxMessageType.DATA);
28 | message.setData(data);
29 | HashMap metaData = new HashMap<>();
30 | metaData.put("channelId", remoteChannelId);
31 | message.setMetaData(metaData);
32 | proxyHandler.getCtx().writeAndFlush(message);
33 | }
34 |
35 | @Override
36 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
37 | NatxMessage message = new NatxMessage();
38 | message.setType(NatxMessageType.DISCONNECTED);
39 | HashMap metaData = new HashMap<>();
40 | metaData.put("channelId", remoteChannelId);
41 | message.setMetaData(metaData);
42 | proxyHandler.getCtx().writeAndFlush(message);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/natx-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | natx
8 | com.xxg
9 | 1.0.3
10 |
11 |
12 | 4.0.0
13 | natx-client
14 |
15 |
16 |
17 | com.xxg
18 | natx-common
19 | 1.0.3
20 |
21 |
22 |
23 |
24 |
25 |
26 | org.apache.maven.plugins
27 | maven-shade-plugin
28 | 2.4.1
29 |
30 |
31 | package
32 |
33 | shade
34 |
35 |
36 |
37 |
38 | com.xxg.natx.client.NatxClientStarter
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/natx-server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | natx
8 | com.xxg
9 | 1.0.3
10 |
11 |
12 | 4.0.0
13 | natx-server
14 |
15 |
16 |
17 | com.xxg
18 | natx-common
19 | 1.0.3
20 |
21 |
22 |
23 |
24 |
25 |
26 | org.apache.maven.plugins
27 | maven-shade-plugin
28 | 2.4.1
29 |
30 |
31 | package
32 |
33 | shade
34 |
35 |
36 |
37 |
38 | com.xxg.natx.server.NatxServerStarter
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/natx-server/src/main/java/com/xxg/natx/server/handler/RemoteProxyHandler.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.server.handler;
2 |
3 | import com.xxg.natx.common.handler.NatxCommonHandler;
4 | import com.xxg.natx.common.protocol.NatxMessage;
5 | import com.xxg.natx.common.protocol.NatxMessageType;
6 | import io.netty.channel.ChannelHandlerContext;
7 |
8 | import java.util.HashMap;
9 |
10 | /**
11 | * Created by wucao on 2019/3/2.
12 | */
13 | public class RemoteProxyHandler extends NatxCommonHandler {
14 |
15 | private NatxCommonHandler proxyHandler;
16 |
17 | public RemoteProxyHandler(NatxCommonHandler proxyHandler) {
18 | this.proxyHandler = proxyHandler;
19 | }
20 |
21 | @Override
22 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
23 | NatxMessage message = new NatxMessage();
24 | message.setType(NatxMessageType.CONNECTED);
25 | HashMap metaData = new HashMap<>();
26 | metaData.put("channelId", ctx.channel().id().asLongText());
27 | message.setMetaData(metaData);
28 | proxyHandler.getCtx().writeAndFlush(message);
29 | }
30 |
31 | @Override
32 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
33 | NatxMessage message = new NatxMessage();
34 | message.setType(NatxMessageType.DISCONNECTED);
35 | HashMap metaData = new HashMap<>();
36 | metaData.put("channelId", ctx.channel().id().asLongText());
37 | message.setMetaData(metaData);
38 | proxyHandler.getCtx().writeAndFlush(message);
39 | }
40 |
41 | @Override
42 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
43 | byte[] data = (byte[]) msg;
44 | NatxMessage message = new NatxMessage();
45 | message.setType(NatxMessageType.DATA);
46 | message.setData(data);
47 | HashMap metaData = new HashMap<>();
48 | metaData.put("channelId", ctx.channel().id().asLongText());
49 | message.setMetaData(metaData);
50 | proxyHandler.getCtx().writeAndFlush(message);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/natx-client/src/main/java/com/xxg/natx/client/NatxClient.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.client;
2 |
3 | import com.xxg.natx.client.handler.NatxClientHandler;
4 | import com.xxg.natx.client.net.TcpConnection;
5 | import com.xxg.natx.common.codec.NatxMessageDecoder;
6 | import com.xxg.natx.common.codec.NatxMessageEncoder;
7 | import io.netty.channel.ChannelFuture;
8 | import io.netty.channel.ChannelInitializer;
9 | import io.netty.channel.socket.SocketChannel;
10 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
11 | import io.netty.handler.timeout.IdleStateHandler;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Created by wucao on 2019/2/27.
17 | */
18 | public class NatxClient {
19 |
20 | public void connect(String serverAddress, int serverPort, String password, int remotePort, String proxyAddress, int proxyPort) throws IOException, InterruptedException {
21 | TcpConnection natxConnection = new TcpConnection();
22 | ChannelFuture future = natxConnection.connect(serverAddress, serverPort, new ChannelInitializer() {
23 | @Override
24 | public void initChannel(SocketChannel ch) throws Exception {
25 | NatxClientHandler natxClientHandler = new NatxClientHandler(remotePort, password,
26 | proxyAddress, proxyPort);
27 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4),
28 | new NatxMessageDecoder(), new NatxMessageEncoder(),
29 | new IdleStateHandler(60, 30, 0), natxClientHandler);
30 | }
31 | });
32 |
33 | // channel close retry connect
34 | future.addListener(future1 -> new Thread() {
35 | @Override
36 | public void run() {
37 | while (true) {
38 | try {
39 | connect(serverAddress, serverPort, password, remotePort, proxyAddress, proxyPort);
40 | break;
41 | } catch (Exception e) {
42 | e.printStackTrace();
43 | try {
44 | Thread.sleep(10000);
45 | } catch (InterruptedException e1) {
46 | e1.printStackTrace();
47 | }
48 | }
49 | }
50 | }
51 | }.start());
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/natx-client/src/main/java/com/xxg/natx/client/NatxClientStarter.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.client;
2 |
3 | import org.apache.commons.cli.*;
4 |
5 | /**
6 | * Created by wucao on 2019/2/27.
7 | */
8 | public class NatxClientStarter {
9 |
10 | public static void main(String[] args) throws Exception {
11 |
12 | // args
13 | Options options = new Options();
14 | options.addOption("h", false, "Help");
15 | options.addOption("server_addr", true, "Natx server address");
16 | options.addOption("server_port", true, "Natx server port");
17 | options.addOption("password", true, "Natx server password");
18 | options.addOption("proxy_addr", true, "Proxy server address");
19 | options.addOption("proxy_port", true, "Proxy server port");
20 | options.addOption("remote_port", true, "Proxy server remote port");
21 |
22 | CommandLineParser parser = new DefaultParser();
23 | CommandLine cmd = parser.parse(options, args);
24 |
25 | if (cmd.hasOption("h")) {
26 | // print help
27 | HelpFormatter formatter = new HelpFormatter();
28 | formatter.printHelp("options", options);
29 | } else {
30 |
31 | String serverAddress = cmd.getOptionValue("server_addr");
32 | if (serverAddress == null) {
33 | System.out.println("server_addr cannot be null");
34 | return;
35 | }
36 | String serverPort = cmd.getOptionValue("server_port");
37 | if (serverPort == null) {
38 | System.out.println("server_port cannot be null");
39 | return;
40 | }
41 | String password = cmd.getOptionValue("password");
42 | String proxyAddress = cmd.getOptionValue("proxy_addr");
43 | if (proxyAddress == null) {
44 | System.out.println("proxy_addr cannot be null");
45 | return;
46 | }
47 | String proxyPort = cmd.getOptionValue("proxy_port");
48 | if (proxyPort == null) {
49 | System.out.println("proxy_port cannot be null");
50 | return;
51 | }
52 | String remotePort = cmd.getOptionValue("remote_port");
53 | if (remotePort == null) {
54 | System.out.println("remote_port cannot be null");
55 | return;
56 | }
57 |
58 | NatxClient client = new NatxClient();
59 | client.connect(serverAddress, Integer.parseInt(serverPort), password, Integer.parseInt(remotePort), proxyAddress, Integer.parseInt(proxyPort));
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/natx-server/src/main/java/com/xxg/natx/server/handler/NatxServerHandler.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.server.handler;
2 |
3 | import com.xxg.natx.common.exception.NatxException;
4 | import com.xxg.natx.common.handler.NatxCommonHandler;
5 | import com.xxg.natx.common.protocol.NatxMessage;
6 | import com.xxg.natx.common.protocol.NatxMessageType;
7 | import com.xxg.natx.server.net.TcpServer;
8 | import io.netty.channel.ChannelHandlerContext;
9 | import io.netty.channel.ChannelInitializer;
10 | import io.netty.channel.group.ChannelGroup;
11 | import io.netty.channel.group.DefaultChannelGroup;
12 | import io.netty.channel.socket.SocketChannel;
13 | import io.netty.handler.codec.bytes.ByteArrayDecoder;
14 | import io.netty.handler.codec.bytes.ByteArrayEncoder;
15 | import io.netty.util.concurrent.GlobalEventExecutor;
16 |
17 | import java.util.HashMap;
18 |
19 | /**
20 | * Created by wucao on 2019/2/27.
21 | */
22 | public class NatxServerHandler extends NatxCommonHandler {
23 |
24 | private TcpServer remoteConnectionServer = new TcpServer();
25 |
26 | private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
27 |
28 | private String password;
29 | private int port;
30 |
31 | private boolean register = false;
32 |
33 | public NatxServerHandler(String password) {
34 | this.password = password;
35 | }
36 |
37 | @Override
38 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
39 |
40 | NatxMessage natxMessage = (NatxMessage) msg;
41 | if (natxMessage.getType() == NatxMessageType.REGISTER) {
42 | processRegister(natxMessage);
43 | } else if (register) {
44 | if (natxMessage.getType() == NatxMessageType.DISCONNECTED) {
45 | processDisconnected(natxMessage);
46 | } else if (natxMessage.getType() == NatxMessageType.DATA) {
47 | processData(natxMessage);
48 | } else if (natxMessage.getType() == NatxMessageType.KEEPALIVE) {
49 | // 心跳包, 不处理
50 | } else {
51 | throw new NatxException("Unknown type: " + natxMessage.getType());
52 | }
53 | } else {
54 | ctx.close();
55 | }
56 | }
57 |
58 | @Override
59 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
60 | remoteConnectionServer.close();
61 | if (register) {
62 | System.out.println("Stop server on port: " + port);
63 | }
64 | }
65 |
66 | /**
67 | * if natxMessage.getType() == NatxMessageType.REGISTER
68 | */
69 | private void processRegister(NatxMessage natxMessage) {
70 | HashMap metaData = new HashMap<>();
71 |
72 | String password = natxMessage.getMetaData().get("password").toString();
73 | if (this.password != null && !this.password.equals(password)) {
74 | metaData.put("success", false);
75 | metaData.put("reason", "Token is wrong");
76 | } else {
77 | int port = (int) natxMessage.getMetaData().get("port");
78 |
79 | try {
80 |
81 | NatxServerHandler thisHandler = this;
82 | remoteConnectionServer.bind(port, new ChannelInitializer() {
83 | @Override
84 | public void initChannel(SocketChannel ch) throws Exception {
85 | ch.pipeline().addLast(new ByteArrayDecoder(), new ByteArrayEncoder(), new RemoteProxyHandler(thisHandler));
86 | channels.add(ch);
87 | }
88 | });
89 |
90 | metaData.put("success", true);
91 | this.port = port;
92 | register = true;
93 | System.out.println("Register success, start server on port: " + port);
94 | } catch (Exception e) {
95 | metaData.put("success", false);
96 | metaData.put("reason", e.getMessage());
97 | e.printStackTrace();
98 | }
99 | }
100 |
101 | NatxMessage sendBackMessage = new NatxMessage();
102 | sendBackMessage.setType(NatxMessageType.REGISTER_RESULT);
103 | sendBackMessage.setMetaData(metaData);
104 | ctx.writeAndFlush(sendBackMessage);
105 |
106 | if (!register) {
107 | System.out.println("Client register error: " + metaData.get("reason"));
108 | ctx.close();
109 | }
110 | }
111 |
112 | /**
113 | * if natxMessage.getType() == NatxMessageType.DATA
114 | */
115 | private void processData(NatxMessage natxMessage) {
116 | channels.writeAndFlush(natxMessage.getData(), channel -> channel.id().asLongText().equals(natxMessage.getMetaData().get("channelId")));
117 | }
118 |
119 | /**
120 | * if natxMessage.getType() == NatxMessageType.DISCONNECTED
121 | * @param natxMessage
122 | */
123 | private void processDisconnected(NatxMessage natxMessage) {
124 | channels.close(channel -> channel.id().asLongText().equals(natxMessage.getMetaData().get("channelId")));
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/natx-client/src/main/java/com/xxg/natx/client/handler/NatxClientHandler.java:
--------------------------------------------------------------------------------
1 | package com.xxg.natx.client.handler;
2 |
3 | import com.xxg.natx.client.net.TcpConnection;
4 | import com.xxg.natx.common.exception.NatxException;
5 | import com.xxg.natx.common.handler.NatxCommonHandler;
6 | import com.xxg.natx.common.protocol.NatxMessage;
7 | import com.xxg.natx.common.protocol.NatxMessageType;
8 | import io.netty.channel.ChannelHandlerContext;
9 | import io.netty.channel.ChannelInitializer;
10 | import io.netty.channel.group.ChannelGroup;
11 | import io.netty.channel.group.DefaultChannelGroup;
12 | import io.netty.channel.socket.SocketChannel;
13 | import io.netty.handler.codec.bytes.ByteArrayDecoder;
14 | import io.netty.handler.codec.bytes.ByteArrayEncoder;
15 | import io.netty.util.concurrent.GlobalEventExecutor;
16 |
17 | import java.util.HashMap;
18 | import java.util.concurrent.ConcurrentHashMap;
19 |
20 | /**
21 | * Created by wucao on 2019/2/27.
22 | */
23 | public class NatxClientHandler extends NatxCommonHandler {
24 |
25 | private int port;
26 | private String password;
27 | private String proxyAddress;
28 | private int proxyPort;
29 |
30 | private ConcurrentHashMap channelHandlerMap = new ConcurrentHashMap<>();
31 | private ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
32 |
33 | public NatxClientHandler(int port, String password, String proxyAddress, int proxyPort) {
34 | this.port = port;
35 | this.password = password;
36 | this.proxyAddress = proxyAddress;
37 | this.proxyPort = proxyPort;
38 | }
39 |
40 | @Override
41 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
42 |
43 | // register client information
44 | NatxMessage natxMessage = new NatxMessage();
45 | natxMessage.setType(NatxMessageType.REGISTER);
46 | HashMap metaData = new HashMap<>();
47 | metaData.put("port", port);
48 | metaData.put("password", password);
49 | natxMessage.setMetaData(metaData);
50 | ctx.writeAndFlush(natxMessage);
51 |
52 | super.channelActive(ctx);
53 | }
54 |
55 | @Override
56 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
57 |
58 | NatxMessage natxMessage = (NatxMessage) msg;
59 | if (natxMessage.getType() == NatxMessageType.REGISTER_RESULT) {
60 | processRegisterResult(natxMessage);
61 | } else if (natxMessage.getType() == NatxMessageType.CONNECTED) {
62 | processConnected(natxMessage);
63 | } else if (natxMessage.getType() == NatxMessageType.DISCONNECTED) {
64 | processDisconnected(natxMessage);
65 | } else if (natxMessage.getType() == NatxMessageType.DATA) {
66 | processData(natxMessage);
67 | } else if (natxMessage.getType() == NatxMessageType.KEEPALIVE) {
68 | // 心跳包, 不处理
69 | } else {
70 | throw new NatxException("Unknown type: " + natxMessage.getType());
71 | }
72 | }
73 |
74 | @Override
75 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
76 | channelGroup.close();
77 | System.out.println("Loss connection to Natx server, Please restart!");
78 | }
79 |
80 | /**
81 | * if natxMessage.getType() == NatxMessageType.REGISTER_RESULT
82 | */
83 | private void processRegisterResult(NatxMessage natxMessage) {
84 | if ((Boolean) natxMessage.getMetaData().get("success")) {
85 | System.out.println("Register to Natx server");
86 | } else {
87 | System.out.println("Register fail: " + natxMessage.getMetaData().get("reason"));
88 | ctx.close();
89 | }
90 | }
91 |
92 | /**
93 | * if natxMessage.getType() == NatxMessageType.CONNECTED
94 | */
95 | private void processConnected(NatxMessage natxMessage) throws Exception {
96 |
97 | try {
98 | NatxClientHandler thisHandler = this;
99 | TcpConnection localConnection = new TcpConnection();
100 | localConnection.connect(proxyAddress, proxyPort, new ChannelInitializer() {
101 | @Override
102 | public void initChannel(SocketChannel ch) throws Exception {
103 | LocalProxyHandler localProxyHandler = new LocalProxyHandler(thisHandler, natxMessage.getMetaData().get("channelId").toString());
104 | ch.pipeline().addLast(new ByteArrayDecoder(), new ByteArrayEncoder(), localProxyHandler);
105 |
106 | channelHandlerMap.put(natxMessage.getMetaData().get("channelId").toString(), localProxyHandler);
107 | channelGroup.add(ch);
108 | }
109 | });
110 | } catch (Exception e) {
111 | NatxMessage message = new NatxMessage();
112 | message.setType(NatxMessageType.DISCONNECTED);
113 | HashMap metaData = new HashMap<>();
114 | metaData.put("channelId", natxMessage.getMetaData().get("channelId"));
115 | message.setMetaData(metaData);
116 | ctx.writeAndFlush(message);
117 | channelHandlerMap.remove(natxMessage.getMetaData().get("channelId"));
118 | throw e;
119 | }
120 | }
121 |
122 | /**
123 | * if natxMessage.getType() == NatxMessageType.DISCONNECTED
124 | */
125 | private void processDisconnected(NatxMessage natxMessage) {
126 | String channelId = natxMessage.getMetaData().get("channelId").toString();
127 | NatxCommonHandler handler = channelHandlerMap.get(channelId);
128 | if (handler != null) {
129 | handler.getCtx().close();
130 | channelHandlerMap.remove(channelId);
131 | }
132 | }
133 |
134 | /**
135 | * if natxMessage.getType() == NatxMessageType.DATA
136 | */
137 | private void processData(NatxMessage natxMessage) {
138 | String channelId = natxMessage.getMetaData().get("channelId").toString();
139 | NatxCommonHandler handler = channelHandlerMap.get(channelId);
140 | if (handler != null) {
141 | ChannelHandlerContext ctx = handler.getCtx();
142 | ctx.writeAndFlush(natxMessage.getData());
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------