├── .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 | --------------------------------------------------------------------------------