├── doc ├── 代理程序通信协议.docx ├── netty-nat时序图.png └── netty-nat网路拓扑图.png ├── nat-server ├── src │ └── main │ │ ├── resources │ │ ├── properties.yml │ │ └── log4j2.xml │ │ └── java │ │ └── server │ │ ├── internal │ │ ├── handler │ │ │ ├── processor │ │ │ │ ├── ConnectionReduceProcessor.java │ │ │ │ ├── ConnectionExpandProcessor.java │ │ │ │ ├── constant │ │ │ │ │ └── ProcessorEnum.java │ │ │ │ ├── HeartbeatProcessor.java │ │ │ │ ├── PreConnectProcessor.java │ │ │ │ ├── UpStreamProcessor.java │ │ │ │ ├── DownStreamProcessor.java │ │ │ │ └── LoginProcessor.java │ │ │ ├── CustomEventHandler.java │ │ │ └── InternalServerHandler.java │ │ ├── decoder │ │ │ ├── ByteToPojoDecoder.java │ │ │ └── PojoToByteEncoder.java │ │ └── InternalNettyServer.java │ │ └── proxy │ │ ├── handler │ │ ├── TcpOutLogHandler.java │ │ ├── HttpProxyServerHandler.java │ │ ├── TcpInLogHandler.java │ │ └── TcpProxyServerHandler.java │ │ └── ProxyNettyServer.java ├── DockerFile └── pom.xml ├── nat-core ├── src │ └── main │ │ └── java │ │ └── core │ │ ├── utils │ │ ├── DateUtil.java │ │ ├── ThreadUtil.java │ │ ├── ArrayUtil.java │ │ ├── BufUtil.java │ │ ├── StringUtil.java │ │ └── ByteUtil.java │ │ ├── netty │ │ ├── group │ │ │ ├── channel │ │ │ │ ├── message │ │ │ │ │ ├── sender │ │ │ │ │ │ └── MessageSender.java │ │ │ │ │ ├── receiver │ │ │ │ │ │ ├── listener │ │ │ │ │ │ │ ├── MessageListener.java │ │ │ │ │ │ │ ├── RequestListener.java │ │ │ │ │ │ │ └── ResponseListener.java │ │ │ │ │ │ └── MessageReceiver.java │ │ │ │ │ ├── ResponseEvent.java │ │ │ │ │ └── MessageContext.java │ │ │ │ └── strategy │ │ │ │ │ ├── ForkStrategy.java │ │ │ │ │ ├── MinLoadForkStrategy.java │ │ │ │ │ ├── RandomForkStrategy.java │ │ │ │ │ ├── KeyBasedForkStrategy.java │ │ │ │ │ ├── RoundRobinForkStrategy.java │ │ │ │ │ ├── constant │ │ │ │ │ └── ForkStrategyEnum.java │ │ │ │ │ └── StrategyManager.java │ │ │ ├── ClientChannelGroup.java │ │ │ └── ServerChannelGroup.java │ │ ├── stater │ │ │ ├── client │ │ │ │ ├── NettyClient.java │ │ │ │ └── BaseClient.java │ │ │ ├── server │ │ │ │ ├── NettyServer.java │ │ │ │ └── BaseServer.java │ │ │ └── factory │ │ │ │ ├── StaterFactory.java │ │ │ │ ├── InternalStaterFactory.java │ │ │ │ └── ProxyStaterFactory.java │ │ └── handler │ │ │ ├── processor │ │ │ ├── Processor.java │ │ │ └── ProcessorManager.java │ │ │ ├── MessageSendFilter.java │ │ │ ├── MessageReceiveFilter.java │ │ │ └── DispatcherHandler.java │ │ ├── crypto │ │ ├── PaddingBytes.java │ │ ├── SimplePaddingBytes.java │ │ └── Crypto.java │ │ ├── entity │ │ ├── Tunnel.java │ │ └── Frame.java │ │ ├── properties │ │ ├── loader │ │ │ ├── AbstractLoader.java │ │ │ ├── PropertiesLoader.java │ │ │ └── YamlLoader.java │ │ ├── cache │ │ │ └── PropertiesCache.java │ │ └── filewatch │ │ │ └── FileWatchService.java │ │ ├── ssl │ │ └── factory │ │ │ └── SslContextFactory.java │ │ └── constant │ │ └── FrameConstant.java └── pom.xml ├── nat-client ├── src │ └── main │ │ ├── java │ │ └── client │ │ │ ├── internal │ │ │ ├── handler │ │ │ │ ├── processor │ │ │ │ │ ├── ConnectionReduceProcessor.java │ │ │ │ │ ├── ConnectionExpandProcessor.java │ │ │ │ │ ├── HeartbeatProcessor.java │ │ │ │ │ ├── ChannelRecycleProcessor.java │ │ │ │ │ ├── constant │ │ │ │ │ │ └── ProcessorEnum.java │ │ │ │ │ ├── LoginProcessor.java │ │ │ │ │ ├── DownStreamProcessor.java │ │ │ │ │ ├── UpStreamProcessor.java │ │ │ │ │ └── PreConnectProcessor.java │ │ │ │ ├── CustomEventHandler.java │ │ │ │ └── InternalClientHandler.java │ │ │ ├── decoder │ │ │ │ ├── ByteToPojoDecoder.java │ │ │ │ └── PojoToByteEncoder.java │ │ │ └── InternalNettyClient.java │ │ │ └── proxy │ │ │ ├── handler │ │ │ ├── TcpOutLogHandler.java │ │ │ ├── TcpInLogHandler.java │ │ │ └── ProxyClientHandler.java │ │ │ └── ProxyNettyClient.java │ │ └── resources │ │ ├── properties.yml │ │ └── log4j2.xml ├── DockerFile └── pom.xml ├── .gitignore ├── docker-compose.yml ├── assembly.xml ├── README.md └── pom.xml /doc/代理程序通信协议.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/likedan130/netty-nat/HEAD/doc/代理程序通信协议.docx -------------------------------------------------------------------------------- /doc/netty-nat时序图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/likedan130/netty-nat/HEAD/doc/netty-nat时序图.png -------------------------------------------------------------------------------- /doc/netty-nat网路拓扑图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/likedan130/netty-nat/HEAD/doc/netty-nat网路拓扑图.png -------------------------------------------------------------------------------- /nat-server/src/main/resources/properties.yml: -------------------------------------------------------------------------------- 1 | #内部通信使用的端口,与客户端的internal.client.port保持一直 2 | internal: 3 | server: 4 | port: 8083 5 | password: '123456' -------------------------------------------------------------------------------- /nat-core/src/main/java/core/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package core.utils; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Created by leesama on 2016/10/13. 7 | */ 8 | public class DateUtil { 9 | } 10 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/sender/MessageSender.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message.sender; 2 | 3 | /** 4 | * @author wneck130@gmail.com 5 | * @Description: 6 | * @date 2021/10/11 7 | */ 8 | public interface MessageSender { 9 | } 10 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/client/NettyClient.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.client; 2 | 3 | /** 4 | * @author wneck130@gmail.com 5 | * @Description: 6 | * @date 2021/9/26 7 | */ 8 | public interface NettyClient { 9 | 10 | void init(); 11 | 12 | void start(); 13 | } 14 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/crypto/PaddingBytes.java: -------------------------------------------------------------------------------- 1 | package core.crypto; 2 | 3 | /** 4 | * 5 | * @author wneck130@gmail.com 6 | */ 7 | public interface PaddingBytes { 8 | 9 | public byte[] addPaddingBytes(byte[] data); 10 | 11 | public byte[] removePaddingBytes(byte[] data); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/server/NettyServer.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.server; 2 | 3 | /** 4 | * @author wneck130@gmail.com 5 | * @Description: 6 | * @date 2021/9/26 7 | */ 8 | public interface NettyServer { 9 | 10 | void init(); 11 | 12 | void start(int port); 13 | 14 | boolean isHeathy(); 15 | 16 | boolean isRunning(); 17 | } 18 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/ForkStrategy.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.group.ChannelGroup; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 9 | * @date 2021/9/28 10 | */ 11 | public interface ForkStrategy { 12 | 13 | Channel fork(ChannelGroup channelGroup); 14 | } 15 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/handler/processor/Processor.java: -------------------------------------------------------------------------------- 1 | package core.netty.handler.processor; 2 | 3 | import core.netty.group.channel.message.receiver.listener.RequestListener; 4 | import core.netty.group.channel.message.receiver.listener.ResponseListener; 5 | 6 | /** 7 | * @Author wneck130@gmail.com 8 | * @function 9 | */ 10 | public interface Processor extends RequestListener, ResponseListener { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/factory/StaterFactory.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.factory; 2 | 3 | import core.netty.stater.client.NettyClient; 4 | import core.netty.stater.server.NettyServer; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 9 | * @date 2021/9/26 10 | */ 11 | public interface StaterFactory { 12 | 13 | NettyServer getServer(); 14 | 15 | NettyClient getClient(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/ConnectionReduceProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | //public class ConnectionReduceProcessor implements Processor { 4 | // 5 | // @Override 6 | // public Frame assemble(ByteBuf in) throws Exception { 7 | // return null; 8 | // } 9 | // 10 | // @Override 11 | // public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 12 | // 13 | // } 14 | //} 15 | -------------------------------------------------------------------------------- /nat-client/DockerFile: -------------------------------------------------------------------------------- 1 | FROM bboysoul/oracle-jdk8:8u311 2 | ADD target/nat-client-1.0-SNAPSHOT.tar / 3 | #确保一下北京时间 4 | RUN /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 5 | #默认开启log4j2全局异步日志,可以根据自己的需求进行调整 6 | ENV JAVA_OPTS="-Xms128M -Xmx4096M -Dog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" 7 | ENV APP_OPTS="" 8 | ENTRYPOINT ["bash", "-c", "java $JAVA_OPTS -Duser.timezone=GMT+08 -jar /nat-client-1.0-SNAPSHOT/nat-client-1.0-SNAPSHOT.jar $APP_OPTS"] -------------------------------------------------------------------------------- /nat-server/DockerFile: -------------------------------------------------------------------------------- 1 | FROM bboysoul/oracle-jdk8:8u311 2 | ADD target/nat-server-1.0-SNAPSHOT.tar / 3 | #确保一下北京时间 4 | RUN /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 5 | #默认开启log4j2全局异步日志,可以根据自己的需求进行调整 6 | ENV JAVA_OPTS="-Xms128M -Xmx4096M -Dog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" 7 | ENV APP_OPTS="" 8 | ENTRYPOINT ["bash", "-c", "java $JAVA_OPTS -Duser.timezone=GMT+08 -jar /nat-server-1.0-SNAPSHOT/nat-server-1.0-SNAPSHOT.jar $APP_OPTS"] -------------------------------------------------------------------------------- /nat-core/src/main/java/core/entity/Tunnel.java: -------------------------------------------------------------------------------- 1 | package core.entity; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author wneck130@gmail.com 7 | * @Description: 通信隧道,从服务端到客户端的全链路形容,以serverPort作为唯一标识 8 | * @date 2021/9/24 9 | */ 10 | @Data 11 | public class Tunnel { 12 | 13 | /** 14 | * 服务端端口号 15 | */ 16 | private int serverPort; 17 | 18 | /** 19 | * 客户端被代理服务IP 20 | */ 21 | private String clientHost; 22 | 23 | /** 24 | * 客户端被代理服务端口号 25 | */ 26 | private int clientPort; 27 | } 28 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/receiver/listener/MessageListener.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message.receiver.listener; 2 | 3 | import core.netty.group.channel.message.receiver.MessageReceiver; 4 | 5 | /** 6 | * @author wneck130@gmail.com 7 | * @Description: 消息监听器 8 | * @date 2021/10/11 9 | */ 10 | public interface MessageListener extends MessageReceiver { 11 | 12 | /** 13 | * 根据数据帧的内容进行消息分类适配 14 | * @param cmd 15 | * @return 16 | */ 17 | boolean supply(byte cmd); 18 | } 19 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/ConnectionReduceProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | /** 4 | * @Author wneck130@gmail.com 5 | * @Function 6 | */ 7 | //public class ConnectionReduceProcessor implements Processor { 8 | // 9 | // @Override 10 | // public Frame assemble(ByteBuf in) throws Exception { 11 | // return null; 12 | // } 13 | // 14 | // @Override 15 | // public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 16 | // 17 | // } 18 | //} 19 | -------------------------------------------------------------------------------- /nat-client/src/main/resources/properties.yml: -------------------------------------------------------------------------------- 1 | #内部连接池大小,内部连接只需转发外部与被代理服务间业务数据,可重用通道 2 | internal: 3 | channel: 4 | init: 5 | num: 10 6 | #服务端ip和端口 7 | server: 8 | host: 127.0.0.1 9 | port: 8083 10 | #隧道信息,一条完整的外部>>服务端>>客户端>>被代理服务间的通路称为隧道 11 | tunnel: 12 | #tunnel示例,代理本地的mysql数据库服务和nacos服务 13 | #服务端监听端口 14 | - serverPort: 9000 15 | #客户端连接的被代理服务端口 16 | clientPort: 3306 17 | #客户端连接的被代理服务端口 18 | clientHost: 127.0.0.1 19 | - serverPort: 9001 20 | clientPort: 8848 21 | clientHost: 127.0.0.1 22 | #接入请求的接入密码 23 | password: '123456' -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/receiver/MessageReceiver.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message.receiver; 2 | 3 | import core.entity.Frame; 4 | import io.netty.buffer.ByteBuf; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 消息接收器 9 | * @date 2021/10/11 10 | */ 11 | public interface MessageReceiver { 12 | 13 | /** 14 | * 接受消息时针对自定义协议解析数据帧,将协议中内容转化成Frame对象为下游提供数据输入 15 | * @param in 16 | * @return 17 | * @throws Exception 18 | */ 19 | Frame assemble(ByteBuf in) throws Exception; 20 | } 21 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/ConnectionExpandProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | /** 4 | * @Author wneck130@gmail.com 5 | * @function 6 | */ 7 | //public class ConnectionExpandProcessor implements Processor { 8 | // 9 | // @Override 10 | // public Frame assemble(ByteBuf in) throws Exception { 11 | // return null; 12 | // } 13 | // 14 | // @Override 15 | // public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 16 | // //客户端对建立连接池命令的响应,无业务需要暂时不实现 17 | // } 18 | //} 19 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/factory/InternalStaterFactory.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.factory; 2 | 3 | import core.netty.stater.client.NettyClient; 4 | import core.netty.stater.server.NettyServer; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 9 | * @date 2021/9/26 10 | */ 11 | public class InternalStaterFactory implements StaterFactory { 12 | @Override 13 | public NettyServer getServer() { 14 | return null; 15 | } 16 | 17 | @Override 18 | public NettyClient getClient() { 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/factory/ProxyStaterFactory.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.factory; 2 | 3 | import core.netty.stater.client.NettyClient; 4 | import core.netty.stater.server.NettyServer; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 9 | * @date 2021/9/26 10 | */ 11 | public class ProxyStaterFactory implements StaterFactory { 12 | 13 | @Override 14 | public NettyServer getServer() { 15 | return null; 16 | } 17 | 18 | @Override 19 | public NettyClient getClient() { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/ConnectionExpandProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | /** 4 | * @Author wneck130@gmail.com 5 | * @Function 6 | */ 7 | //public class ConnectionExpandProcessor implements Processor { 8 | // 9 | // @Override 10 | // public Frame assemble(ByteBuf in) throws Exception { 11 | // return null; 12 | // } 13 | // 14 | // @Override 15 | // public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 16 | // //客户端对建立连接池命令的响应,无业务需要暂时不实现 17 | // } 18 | //} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | netty-log/* 3 | target/** 4 | !**/src/main/** 5 | !**/src/test/** 6 | mvnw* 7 | */mvnw* 8 | .mvn/ 9 | *.class 10 | out/ 11 | 12 | ### STS ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | 27 | ### NetBeans ### 28 | /nbproject/private/ 29 | /nbbuild/ 30 | /dist/ 31 | /nbdist/ 32 | /.nb-gradle/ 33 | build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | nat-client/target/ 38 | nat-core/target/ 39 | nat-server/target/ 40 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | natServer: 5 | build: 6 | context: nat-server 7 | dockerfile: DockerFile 8 | image: netty-nat-server 9 | container_name: netty-nat-server 10 | ports: 11 | #内部通信使用的端口,根据resources下properties.yml中配置internal.server.port自主调整 12 | - 8083:8083 13 | #隧道监听的外部端口,根据nat-client中tunnel配置自主调整 14 | - 9000:9000 15 | - 9001:9001 16 | natClient: 17 | build: 18 | context: nat-client 19 | dockerfile: DockerFile 20 | image: netty-nat-client 21 | container_name: netty-nat-client -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/receiver/listener/RequestListener.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message.receiver.listener; 2 | 3 | import core.entity.Frame; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 请求类消息监听器 9 | * @date 2021/10/11 10 | */ 11 | public interface RequestListener extends MessageListener { 12 | 13 | /** 14 | * 消息请求处理方法 15 | * 16 | * @param ctx netty channel上下文 17 | * @param msg 解析成Frame结构的TCP请求数据帧 18 | * @throws Exception 19 | */ 20 | default void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/MinLoadForkStrategy.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.group.ChannelGroup; 5 | 6 | import java.util.Comparator; 7 | 8 | /** 9 | * @author wneck130@gmail.com 10 | * @Description: 11 | * @date 2021/9/28 12 | */ 13 | public class MinLoadForkStrategy implements ForkStrategy{ 14 | @Override 15 | public Channel fork(ChannelGroup channelGroup) { 16 | if (channelGroup == null) { 17 | return null; 18 | } 19 | return channelGroup.stream().min(Comparator.comparingLong(Channel::bytesBeforeWritable)).get(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/receiver/listener/ResponseListener.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message.receiver.listener; 2 | 3 | import core.netty.group.channel.message.ResponseEvent; 4 | 5 | /** 6 | * @author wneck130@gmail.com 7 | * @Description: 响应类消息监听器 8 | * @date 2021/10/11 9 | */ 10 | public interface ResponseListener extends MessageListener{ 11 | 12 | /** 13 | * 消息响应超时处理方法 14 | * @param responseEvent 15 | */ 16 | default void timeout(ResponseEvent responseEvent) {} 17 | 18 | /** 19 | * 消息响应处理方法,响应内容与对应的请求内容封装在ResponseEvent中 20 | * @param responseEvent 21 | */ 22 | default void response(ResponseEvent responseEvent) {} 23 | } 24 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/RandomForkStrategy.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.group.ChannelGroup; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | /** 11 | * @author wneck130@gmail.com 12 | * @Description: 13 | * @date 2021/9/28 14 | */ 15 | public class RandomForkStrategy implements ForkStrategy { 16 | 17 | @Override 18 | public Channel fork(ChannelGroup channelGroup) { 19 | if (channelGroup == null) { 20 | return null; 21 | } 22 | List list = new ArrayList<>(channelGroup); 23 | return list.get(new Random().nextInt(list.size())); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/utils/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package core.utils; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | /** 7 | * 8 | * @author wneck130@gmail.com 9 | */ 10 | public class ThreadUtil { 11 | 12 | /** 13 | * close the thread pool safely. 14 | * @param pool 15 | */ 16 | public static void safeClose(ExecutorService pool) { 17 | if(pool != null){ 18 | pool.shutdown(); 19 | try{ 20 | if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { 21 | pool.shutdownNow(); 22 | } 23 | }catch(InterruptedException ex){ 24 | //ignore the ex 25 | } 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/utils/ArrayUtil.java: -------------------------------------------------------------------------------- 1 | package core.utils; 2 | 3 | /** 4 | * @author xian 5 | * 2020/07/20 6 | * 数组工具类 7 | */ 8 | public class ArrayUtil { 9 | 10 | /** 11 | * 数组是否包含某元素 12 | * @param array 13 | * @param value 14 | * @return 15 | */ 16 | public static boolean contains(byte[] array, byte value) { 17 | return indexOf(array, value) > -1; 18 | } 19 | 20 | /** 21 | * 循环遍历 22 | * @param array 23 | * @param value 24 | * @return 25 | */ 26 | public static int indexOf(byte[] array, byte value) { 27 | if (null != array) { 28 | for(int i = 0; i < array.length; ++i) { 29 | if (value == array[i]) { 30 | return i; 31 | } 32 | } 33 | } 34 | return -1; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/ResponseEvent.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message; 2 | 3 | import core.entity.Frame; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.EventObject; 8 | 9 | /** 10 | * @author wneck130@gmail.com 11 | * @Description: 12 | * @date 2021/9/28 13 | */ 14 | @Setter 15 | @Getter 16 | public class ResponseEvent extends EventObject { 17 | 18 | private Frame request; 19 | 20 | private Frame response; 21 | 22 | private String channelId; 23 | 24 | /** 25 | * Constructs a prototypical Event. 26 | * 27 | * @param source The object on which the Event initially occurred. 28 | * @throws IllegalArgumentException if source is null. 29 | */ 30 | public ResponseEvent(Object source) { 31 | super(source); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/KeyBasedForkStrategy.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.group.ChannelGroup; 5 | import lombok.Setter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * @author wneck130@gmail.com 12 | * @Description: 13 | * @date 2021/9/28 14 | */ 15 | @Setter 16 | public class KeyBasedForkStrategy implements ForkStrategy{ 17 | 18 | private String key; 19 | 20 | @Override 21 | public Channel fork(ChannelGroup channelGroup) { 22 | if (channelGroup == null || channelGroup.size() == 0) { 23 | return null; 24 | } 25 | List list = new ArrayList<>(channelGroup); 26 | return list.get(Math.abs(key.hashCode()%channelGroup.size())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/proxy/handler/TcpOutLogHandler.java: -------------------------------------------------------------------------------- 1 | package client.proxy.handler; 2 | 3 | import core.utils.BufUtil; 4 | import core.utils.ByteUtil; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelOutboundHandlerAdapter; 8 | import io.netty.channel.ChannelPromise; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * @author wneck130@gmail.com 13 | * @Description: 14 | * @date 2021/9/27 15 | */ 16 | @Slf4j 17 | public class TcpOutLogHandler extends ChannelOutboundHandlerAdapter { 18 | 19 | @Override 20 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 21 | log.debug("ProxyClient发送消息:{}", ByteUtil.toHexString(BufUtil.getArray((ByteBuf) msg))); 22 | super.write(ctx, msg, promise); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/proxy/handler/TcpOutLogHandler.java: -------------------------------------------------------------------------------- 1 | package server.proxy.handler; 2 | 3 | import core.utils.BufUtil; 4 | import core.utils.ByteUtil; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelOutboundHandlerAdapter; 8 | import io.netty.channel.ChannelPromise; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * @author wneck130@gmail.com 13 | * @Description: 14 | * @date 2021/9/27 15 | */ 16 | @Slf4j 17 | public class TcpOutLogHandler extends ChannelOutboundHandlerAdapter { 18 | 19 | @Override 20 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 21 | log.debug("ProxyServer发送消息:{}", ByteUtil.toHexString(BufUtil.getArray((ByteBuf) msg))); 22 | super.write(ctx, msg, promise); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/CustomEventHandler.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.handler.timeout.IdleState; 6 | import io.netty.handler.timeout.IdleStateEvent; 7 | 8 | /** 9 | * @Author wneck130@gmail.com 10 | * @Function 用户自定义事件处理器 11 | */ 12 | public class CustomEventHandler extends ChannelInboundHandlerAdapter { 13 | 14 | @Override 15 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 16 | //监测到连接空闲,则断开连接 17 | if (evt instanceof IdleStateEvent) { 18 | IdleStateEvent event = (IdleStateEvent) evt; 19 | if (event.state() == IdleState.ALL_IDLE) { 20 | ctx.close(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/handler/MessageSendFilter.java: -------------------------------------------------------------------------------- 1 | package core.netty.handler; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.netty.group.channel.message.MessageContext; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelOutboundHandlerAdapter; 8 | import io.netty.channel.ChannelPromise; 9 | 10 | /** 11 | * @author wneck130@gmail.com 12 | * @Description: 消息请求过滤器 13 | * 每一条发送的消息存储至 {@link core.netty.group.channel.message.MessageContext}的历史消息中等待响应 14 | * @date 2021/9/30 15 | */ 16 | public class MessageSendFilter extends ChannelOutboundHandlerAdapter { 17 | 18 | @Override 19 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 20 | Frame frame = (Frame) msg; 21 | if (frame.getPv() == FrameConstant.REQ_PV) { 22 | MessageContext.addHistoryFrame(frame.getReq(), frame); 23 | } 24 | super.write(ctx, msg, promise); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/RoundRobinForkStrategy.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.group.ChannelGroup; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | /** 12 | * @author wneck130@gmail.com 13 | * @Description: 14 | * @date 2021/9/28 15 | */ 16 | public class RoundRobinForkStrategy implements ForkStrategy { 17 | 18 | private static AtomicInteger lastIndex = new AtomicInteger(0); 19 | 20 | @Override 21 | public Channel fork(ChannelGroup channelGroup) { 22 | if (channelGroup == null) { 23 | return null; 24 | } 25 | if (lastIndex.get() > channelGroup.size()) { 26 | lastIndex = new AtomicInteger(0); 27 | } 28 | List list = new ArrayList<>(channelGroup); 29 | return list.get(new Random().nextInt(lastIndex.getAndIncrement())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/constant/ForkStrategyEnum.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy.constant; 2 | 3 | import core.netty.group.channel.strategy.KeyBasedForkStrategy; 4 | import core.netty.group.channel.strategy.MinLoadForkStrategy; 5 | import core.netty.group.channel.strategy.RandomForkStrategy; 6 | import core.netty.group.channel.strategy.RoundRobinForkStrategy; 7 | import lombok.Getter; 8 | 9 | /** 10 | * @author wneck130@gmail.com 11 | * @Description: 12 | * @date 2021/9/28 13 | */ 14 | @Getter 15 | public enum ForkStrategyEnum { 16 | 17 | RANDOM("随机获取", RandomForkStrategy.class), 18 | MIN_LOAD("最小负载", MinLoadForkStrategy.class), 19 | ROUND_ROBIN("轮询", RoundRobinForkStrategy.class), 20 | KEY("根据Key获取", KeyBasedForkStrategy.class); 21 | 22 | /** 23 | * 策略名 24 | */ 25 | private String name; 26 | 27 | /** 28 | * 策略实现类 29 | */ 30 | private Class clazz; 31 | 32 | ForkStrategyEnum (String name, Class clazz) { 33 | this.name = name; 34 | this.clazz = clazz; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/crypto/SimplePaddingBytes.java: -------------------------------------------------------------------------------- 1 | package core.crypto; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * PaddingBytes for LSD company. 7 | * 8 | * @author wneck130@gmail.com 9 | */ 10 | public class SimplePaddingBytes implements PaddingBytes { 11 | 12 | private static final int KEY_SIZE = 16; 13 | 14 | @Override 15 | public byte[] addPaddingBytes(byte[] plain) { 16 | int size = plain.length; 17 | int remainder = size % KEY_SIZE; 18 | if (remainder == 0) { 19 | return plain; 20 | } else { 21 | int padding = KEY_SIZE - remainder; 22 | byte[] result = Arrays.copyOf(plain, size + padding); 23 | return result; 24 | } 25 | } 26 | 27 | @Override 28 | public byte[] removePaddingBytes(byte[] plain) { 29 | int count = 0; 30 | for (int i = plain.length - 1; i >= 0; i--) { 31 | if (plain[i] == (byte) 0x00) { 32 | count++; 33 | } else { 34 | break; 35 | } 36 | } 37 | return Arrays.copyOfRange(plain, 0, plain.length - count); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /nat-client/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /nat-server/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/decoder/ByteToPojoDecoder.java: -------------------------------------------------------------------------------- 1 | package server.internal.decoder; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.netty.handler.processor.ProcessorManager; 6 | import core.utils.BufUtil; 7 | import core.utils.ByteUtil; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.handler.codec.ByteToMessageDecoder; 11 | import lombok.extern.slf4j.Slf4j; 12 | import server.internal.handler.processor.constant.ProcessorEnum; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * @Author wneck130@gmail.com 18 | * @function 解码器,字节转Java对象 19 | */ 20 | @Slf4j 21 | public class ByteToPojoDecoder extends ByteToMessageDecoder { 22 | 23 | @Override 24 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 25 | if (in.readableBytes() < FrameConstant.FRAME_MIN_LEN) { 26 | log.error("无法解析的消息 : " + ByteUtil.toHexString(BufUtil.getArray(in))); 27 | ctx.close(); 28 | return; 29 | } 30 | log.debug("收到消息:{}", ByteUtil.toHexString(BufUtil.getArray(in))); 31 | byte cmd = in.getByte(FrameConstant.FRAME_CMD_INDEX); 32 | Frame frame = ProcessorManager.getInstance(ProcessorEnum.getClassByCmd(cmd)).assemble(in); 33 | out.add(frame); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/decoder/ByteToPojoDecoder.java: -------------------------------------------------------------------------------- 1 | package client.internal.decoder; 2 | 3 | import client.internal.handler.processor.constant.ProcessorEnum; 4 | import core.constant.FrameConstant; 5 | import core.entity.Frame; 6 | import core.netty.handler.processor.ProcessorManager; 7 | import core.utils.BufUtil; 8 | import core.utils.ByteUtil; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.handler.codec.ByteToMessageDecoder; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * @Author wneck130@gmail.com 18 | * @function 解码器,字节转Java对象 19 | */ 20 | @Slf4j 21 | public class ByteToPojoDecoder extends ByteToMessageDecoder { 22 | 23 | @Override 24 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 25 | if (in.readableBytes() < FrameConstant.FRAME_MIN_LEN) { 26 | log.error("invalidate msg : " + ByteUtil.toHexString(BufUtil.getArray(in))); 27 | ctx.close(); 28 | return; 29 | } 30 | log.debug("收到消息:{}", ByteUtil.toHexString(BufUtil.getArray(in))); 31 | byte cmd = in.getByte(FrameConstant.FRAME_CMD_INDEX); 32 | Frame frame = ProcessorManager.getInstance(ProcessorEnum.getClassByCmd(cmd)).assemble(in); 33 | out.add(frame); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/handler/processor/ProcessorManager.java: -------------------------------------------------------------------------------- 1 | package core.netty.handler.processor; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 9 | * @date 2021/9/24 10 | */ 11 | public class ProcessorManager { 12 | 13 | private static Map instances = new ConcurrentHashMap<>(); 14 | 15 | /** 16 | * @param clazz 17 | * @return 18 | * @throws InstantiationException 19 | * @throws IllegalAccessException 20 | */ 21 | @SuppressWarnings("unchecked") 22 | public static Processor getInstance(Class clazz) { 23 | Object instance = instances.get(clazz); 24 | if (instance == null) { 25 | synchronized (ProcessorManager.class) { 26 | instance = instances.get(clazz); 27 | if (instance == null) { 28 | try { 29 | instance = clazz.newInstance(); 30 | } catch (InstantiationException e) { 31 | e.printStackTrace(); 32 | } catch (IllegalAccessException e) { 33 | e.printStackTrace(); 34 | } 35 | instances.put(clazz, (Processor) instance); 36 | } 37 | } 38 | } 39 | return (Processor) instance; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/properties/loader/AbstractLoader.java: -------------------------------------------------------------------------------- 1 | package core.properties.loader; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * ClassName: AbstructLoader 9 | * Function: 文件加载器,可以用于加载配置文件(.properties)或通信协议模板文件(.xml). 10 | * date: 2017年3月20日 上午10:43:14 11 | * 12 | * @author wneck130@gmail.com 13 | * @version 14 | * @since JDK 1.8 15 | */ 16 | public abstract class AbstractLoader { 17 | 18 | /** 19 | * 加载配置文件的名称 20 | */ 21 | protected String[] targetFilenames; 22 | 23 | public AbstractLoader(String... targetFilenames) { 24 | this.targetFilenames = targetFilenames; 25 | } 26 | 27 | /** 28 | * 加载文件,将文件内容缓存入内存中供程序使用 29 | */ 30 | public abstract void load(String path) throws Exception; 31 | 32 | /** 33 | * 重载文件,当需要实时监测文件的修改状态时,实现本方法 34 | * 可以在文件修改事件触发的时候重载文件,刷新缓存在内存中的文件内容 35 | */ 36 | public abstract void reload(String path) throws Exception; 37 | 38 | public boolean match(String filename) { 39 | return Arrays.stream(targetFilenames).anyMatch(targetFilename -> Pattern.matches(targetFilename, filename)); 40 | } 41 | 42 | public static void main(String[] args) { 43 | String filename = "properties.yml"; 44 | String[] targetFilenames = new String[]{"properties.yml", "properties.properties"}; 45 | System.out.println(Arrays.stream(targetFilenames).anyMatch(targetFilename -> Pattern.matches(targetFilename, filename))); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/strategy/StrategyManager.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.strategy; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author wneck130@gmail.com 8 | * @Description: 9 | * @date 2021/9/28 10 | */ 11 | public class StrategyManager { 12 | 13 | private static Map, ForkStrategy> instances = new ConcurrentHashMap<>(); 14 | 15 | /** 16 | * @param clazz 17 | * @return 18 | * @throws InstantiationException 19 | * @throws IllegalAccessException 20 | */ 21 | @SuppressWarnings("unchecked") 22 | public static ForkStrategy getInstance(Class clazz) { 23 | Object instance = instances.get(clazz); 24 | if (instance == null) { 25 | synchronized (StrategyManager.class) { 26 | instance = instances.get(clazz); 27 | if (instance == null) { 28 | try { 29 | instance = clazz.newInstance(); 30 | } catch (InstantiationException e) { 31 | e.printStackTrace(); 32 | } catch (IllegalAccessException e) { 33 | e.printStackTrace(); 34 | } 35 | instances.put(clazz, (ForkStrategy) instance); 36 | } 37 | } 38 | } 39 | return (ForkStrategy) instance; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/CustomEventHandler.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler; 2 | 3 | import client.internal.handler.processor.constant.ProcessorEnum; 4 | import core.constant.FrameConstant; 5 | import core.entity.Frame; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | 11 | /** 12 | * @Author wneck130@gmail.com 13 | * @function 14 | */ 15 | public class CustomEventHandler extends ChannelInboundHandlerAdapter { 16 | /** 17 | * 事件触发处理方法,向pipeline注册相关事件的监听,触发后使用本方法进行处理 18 | * 19 | * @param ctx 20 | * @param evt 21 | * @throws Exception 22 | */ 23 | @Override 24 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 25 | //channel触发空闲时,发送心跳,确保连接正常 26 | if (evt instanceof IdleStateEvent) { 27 | IdleStateEvent event = (IdleStateEvent) evt; 28 | if (event.state() == IdleState.ALL_IDLE) { 29 | Frame frame = new Frame(); 30 | frame.setCmd(ProcessorEnum.HEARTBEAT.getCmd()); 31 | frame.setReq(ctx.channel().id().asShortText()); 32 | frame.setRes(FrameConstant.DEFAULT_CHANNEL_ID); 33 | ctx.writeAndFlush(frame); 34 | } 35 | } else { 36 | super.userEventTriggered(ctx, evt); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/proxy/handler/HttpProxyServerHandler.java: -------------------------------------------------------------------------------- 1 | package server.proxy.handler; 2 | 3 | import core.netty.group.ServerChannelGroup; 4 | import core.netty.group.channel.strategy.constant.ForkStrategyEnum; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 9 | import io.netty.handler.codec.http.FullHttpRequest; 10 | import io.netty.handler.codec.http.HttpResponseStatus; 11 | import io.netty.handler.codec.http.HttpVersion; 12 | 13 | import static io.netty.handler.codec.http.HttpUtil.is100ContinueExpected; 14 | 15 | /** 16 | * @author wneck130@gmail.com 17 | * @Description: 18 | * @date 2021/9/27 19 | */ 20 | public class HttpProxyServerHandler extends SimpleChannelInboundHandler { 21 | 22 | @Override 23 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { 24 | //处理100 continue请求 25 | if (is100ContinueExpected(msg)) { 26 | ctx.writeAndFlush(new DefaultFullHttpResponse( 27 | HttpVersion.HTTP_1_1, 28 | HttpResponseStatus.CONTINUE)); 29 | } 30 | //收到外部的msg消息,首先挑选一条internalChannel准备进行数据转发 31 | Channel internalChannel = ServerChannelGroup.forkChannel(ForkStrategyEnum.MIN_LOAD); 32 | //TODO 可在此添加代码修改http请求 33 | internalChannel.writeAndFlush(msg); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/HeartbeatProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | import client.internal.handler.processor.constant.ProcessorEnum; 4 | import core.entity.Frame; 5 | import core.netty.handler.processor.Processor; 6 | import core.utils.BufUtil; 7 | import core.utils.ByteUtil; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.util.Objects; 13 | import java.util.UnknownFormatConversionException; 14 | 15 | /** 16 | * @Author wneck130@gmail.com 17 | * @function 心跳命令处理器(cmd:0x02),命令由internalClient发起请求,internalServer回复响应 18 | */ 19 | @Slf4j 20 | public class HeartbeatProcessor implements Processor { 21 | 22 | @Override 23 | public Frame assemble(ByteBuf in) throws Exception { 24 | try { 25 | //解析协议公共部分 26 | Frame frame = new Frame().quickHead(in); 27 | return frame; 28 | } catch (Exception e) { 29 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 30 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 31 | } 32 | } 33 | 34 | @Override 35 | public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 36 | //TODO 预留心跳命令,对于长连接的保持有特殊需要时启用心跳 37 | } 38 | 39 | @Override 40 | public boolean supply(byte cmd) { 41 | return Objects.equals(ProcessorEnum.HEARTBEAT.getCmd(), cmd); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/ChannelRecycleProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | /** 4 | * @Author wneck130@gmail.com 5 | * @function internal连接回收命令处理器,server端检测到连接断开后发送给客户端,确保无用tcp连接被回收 6 | */ 7 | //@Slf4j 8 | //public class ChannelRecycleProcessor implements Processor { 9 | // 10 | // @Override 11 | // public Frame assemble(ByteBuf in) throws Exception { 12 | // return null; 13 | // } 14 | // 15 | // @Override 16 | // public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 17 | // Channel internalChannel = ctx.channel(); 18 | // log.debug("InternalChannel:"+ internalChannel.id() + " 回收连接!!!"); 19 | // if (ClientChannelGroup.channelPairExist(internalChannel.id())) { 20 | // ClientChannelGroup.printGroupState(); 21 | // Channel proxyChannel = ClientChannelGroup.getProxyByInternal(internalChannel.id()); 22 | // ClientChannelGroup.removeProxyChannel(proxyChannel); 23 | // if (internalChannel == null) { 24 | // log.error("与internalChannel:"+internalChannel.id()+"配对的proxyChannel为null"); 25 | // return; 26 | // } 27 | // ClientChannelGroup.removeChannelPair(internalChannel.id(), ctx.channel().id()); 28 | // ClientChannelGroup.releaseInternalChannel(internalChannel); 29 | // } else { 30 | // log.error("internalChannel:"+ internalChannel.id() + " 未找到配对关系!!!"); 31 | // ClientChannelGroup.printGroupState(); 32 | // } 33 | // } 34 | //} 35 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/handler/MessageReceiveFilter.java: -------------------------------------------------------------------------------- 1 | package core.netty.handler; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.netty.group.channel.message.MessageContext; 6 | import core.netty.group.channel.message.ResponseEvent; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * @author wneck130@gmail.com 13 | * @Description: 消息响应过滤器,推送消息响应事件 14 | * @date 2021/9/28 15 | */ 16 | @Slf4j 17 | public class MessageReceiveFilter extends SimpleChannelInboundHandler { 18 | 19 | @Override 20 | protected void channelRead0(ChannelHandlerContext ctx, Frame msg) throws Exception { 21 | try { 22 | //服务端主动发送的消息才需要监听响应 23 | if (msg.getPv() == FrameConstant.RES_PV) { 24 | //生成事件源 25 | MessageContext messageContext = MessageContext.getInstance(); 26 | //生成对应的responseEvent 27 | String channelId = msg.getReq(); 28 | ResponseEvent responseEvent = new ResponseEvent(messageContext); 29 | responseEvent.setChannelId(channelId); 30 | responseEvent.setRequest(MessageContext.getHistoryFrame(channelId, msg.getSerial())); 31 | responseEvent.setResponse(msg); 32 | messageContext.notifyResponse(responseEvent); 33 | return; 34 | } 35 | ctx.fireChannelRead(msg); 36 | } catch (Exception e) { 37 | log.error("{}\n生成响应消息事件失败", msg.toString(), e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/properties/cache/PropertiesCache.java: -------------------------------------------------------------------------------- 1 | package core.properties.cache; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.*; 5 | 6 | import core.utils.ByteUtil; 7 | 8 | /** 9 | * Project Name: nat-core 10 | * Package Name: core.properties.cache 11 | * ClassName: PropertiesCache 12 | * Function: 缓存项目的全局配置信息. 13 | * date: 2017/3/15 10:53 14 | * 15 | * @author wneck130@gmail.com 16 | * @since JDK 1.8 17 | */ 18 | public class PropertiesCache { 19 | 20 | /** 21 | * 私有的空参构造函数 22 | */ 23 | private PropertiesCache() { 24 | } 25 | 26 | /** 27 | * 单例 28 | * 29 | * @return 30 | */ 31 | public static PropertiesCache getInstance() { 32 | return Holder.INSTANCE; 33 | } 34 | 35 | /** 36 | * 懒加载 37 | * 38 | * @author songw 39 | */ 40 | private static class Holder { 41 | final static PropertiesCache INSTANCE = new PropertiesCache(); 42 | } 43 | 44 | private final ConcurrentMap props = new ConcurrentHashMap<>(); 45 | 46 | 47 | public ConcurrentMap getProps() { 48 | return props; 49 | } 50 | 51 | public String get(String key) { 52 | return props.get(key).toString(); 53 | } 54 | 55 | public Integer getInt(String key) { 56 | return Integer.valueOf(this.get(key)); 57 | } 58 | 59 | public Byte getByte(String key) { 60 | return ByteUtil.parseHexString(this.get(key)); 61 | } 62 | 63 | public byte[] getBytes(String key) { 64 | return ByteUtil.parseHexStringToArray(this.get(key)); 65 | } 66 | 67 | public List getList(String key) { 68 | return (List) props.get(key); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/ssl/factory/SslContextFactory.java: -------------------------------------------------------------------------------- 1 | package core.ssl.factory; 2 | 3 | import javax.net.ssl.KeyManagerFactory; 4 | import javax.net.ssl.SSLContext; 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.security.KeyStore; 8 | 9 | /** 10 | * Project Name: nat-core 11 | * Package Name: core.factory 12 | * ClassName: SslContextFactory 13 | * Function: TODO ADD FUNCTION. 14 | * date: 2017/4/24 16:24 15 | * @author wneck130@gmail.com 16 | * @since JDK 1.8 17 | */ 18 | public class SslContextFactory { 19 | 20 | private static final String PROTOCOL = "TLSv1.2"; 21 | private static final SSLContext SERVER_CONTEXT; 22 | 23 | static{ 24 | SSLContext serverContext = null; 25 | // get keystore locations and passwords 26 | String keyStorePassword = "hadlinks"; 27 | String sep = File.separator; 28 | try{ 29 | KeyStore ks = KeyStore.getInstance("JKS"); 30 | //加载jks文件,路径:工程目录/cert/server.jks 31 | ks.load(new FileInputStream(System.getProperty("user.dir")+sep+"cert"+sep+"server.jks"), keyStorePassword.toCharArray()); 32 | // Set up key manager factory to use our key store 33 | KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 34 | kmf.init(ks, keyStorePassword.toCharArray()); 35 | serverContext = SSLContext.getInstance(PROTOCOL); 36 | serverContext.init(kmf.getKeyManagers(), null, null); 37 | } catch (Exception e){ 38 | throw new Error("Failed to initialize the server-side SSLContext", e); 39 | } 40 | SERVER_CONTEXT = serverContext; 41 | } 42 | 43 | public static SSLContext getServerContext(){ 44 | return SERVER_CONTEXT; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/constant/ProcessorEnum.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor.constant; 2 | 3 | import client.internal.handler.processor.*; 4 | import core.netty.handler.processor.Processor; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * @author wneck130@gmail.com 10 | * @Description: 11 | * @date 2021/9/24 12 | */ 13 | public enum ProcessorEnum { 14 | /** 15 | * 登录处理器 16 | */ 17 | LOGIN((byte)0x01, LoginProcessor.class), 18 | /** 19 | * 心跳处理器 20 | */ 21 | HEARTBEAT((byte)0x02, HeartbeatProcessor.class), 22 | /** 23 | * 客户端预连接被代理服务处理器 24 | */ 25 | PRE_CONNECT((byte)0x03, PreConnectProcessor.class), 26 | /** 27 | * 数据传输处理器 28 | */ 29 | DOWN_STREAM((byte)0xEE, DownStreamProcessor.class), 30 | /** 31 | * 32 | */ 33 | UP_STREAM((byte)0xFF, UpStreamProcessor.class) 34 | ; 35 | 36 | private byte cmd; 37 | 38 | private Class clazz; 39 | 40 | ProcessorEnum(byte cmd, Class clazz) { 41 | this.cmd = cmd; 42 | this.clazz = clazz; 43 | } 44 | 45 | public byte getCmd() { 46 | return cmd; 47 | } 48 | 49 | public Class getClazz() { 50 | return clazz; 51 | } 52 | 53 | /** 54 | * 根据cmd获取对应class对象 55 | * @param cmd 56 | * @return 57 | */ 58 | public static Class getClassByCmd(byte cmd) { 59 | for (ProcessorEnum processorEnum : ProcessorEnum.values()) { 60 | if (Objects.equals(processorEnum.getCmd(), cmd)) { 61 | return processorEnum.getClazz(); 62 | } 63 | } 64 | throw new EnumConstantNotPresentException(ProcessorEnum.class, cmd + "枚举常量不存在"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/constant/ProcessorEnum.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor.constant; 2 | 3 | import core.netty.handler.processor.Processor; 4 | import server.internal.handler.processor.*; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * @author wneck130@gmail.com 10 | * @Description: 11 | * @date 2021/9/24 12 | */ 13 | public enum ProcessorEnum { 14 | /** 15 | * 登录处理器 16 | */ 17 | LOGIN((byte)0x01, LoginProcessor.class), 18 | /** 19 | * 心跳处理器 20 | */ 21 | HEARTBEAT((byte)0x02, HeartbeatProcessor.class), 22 | /** 23 | * 预创建连接 24 | */ 25 | PRE_CONNECT((byte)0x03, PreConnectProcessor.class), 26 | /** 27 | * 数据下发处理器 28 | */ 29 | DOWN_STREAM((byte)0xEE, DownStreamProcessor.class), 30 | /** 31 | * 数据上行处理器 32 | */ 33 | UP_STREAM((byte)0xFF, UpStreamProcessor.class), 34 | ; 35 | 36 | private byte cmd; 37 | 38 | private Class clazz; 39 | 40 | ProcessorEnum(byte cmd, Class clazz) { 41 | this.cmd = cmd; 42 | this.clazz = clazz; 43 | } 44 | 45 | public byte getCmd() { 46 | return cmd; 47 | } 48 | 49 | public Class getClazz() { 50 | return clazz; 51 | } 52 | 53 | /** 54 | * 根据cmd获取对应class对象 55 | * @param cmd 56 | * @return 57 | */ 58 | public static Class getClassByCmd(byte cmd) { 59 | for (ProcessorEnum processorEnum : ProcessorEnum.values()) { 60 | if (Objects.equals(processorEnum.getCmd(), cmd)) { 61 | return processorEnum.getClazz(); 62 | } 63 | } 64 | throw new EnumConstantNotPresentException(ProcessorEnum.class, cmd + "枚举常量不存在"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/properties/filewatch/FileWatchService.java: -------------------------------------------------------------------------------- 1 | package core.properties.filewatch; 2 | 3 | import java.nio.file.FileSystems; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | import java.nio.file.StandardWatchEventKinds; 7 | import java.nio.file.WatchEvent; 8 | import java.nio.file.WatchEvent.Kind; 9 | import java.nio.file.WatchKey; 10 | import java.nio.file.WatchService; 11 | import org.apache.logging.log4j.*; 12 | import core.properties.loader.AbstractLoader; 13 | 14 | /** 15 | * ClassName: FileWatchService 16 | * Function: 文件监听服务,可以对某个目录下的文件进行监听,发生修改事件后触发任务. 17 | * date: 2017年3月18日 下午4:21:15 18 | * 19 | * @author wneck130@gmail.com 20 | * @version 21 | * @since JDK 1.8 22 | */ 23 | public class FileWatchService { 24 | 25 | private final Logger logger = LogManager.getLogger(FileWatchService.class); 26 | private WatchService watchService = null; 27 | 28 | public void addWatcher(String pathStr, AbstractLoader loader){ 29 | Path path = Paths.get(pathStr); 30 | try{ 31 | watchService = FileSystems.getDefault().newWatchService(); 32 | path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); 33 | WatchKey key = null; 34 | while(true){ 35 | key = watchService.take(); 36 | for(WatchEvent event : key.pollEvents()){ 37 | Kind kind = event.kind(); 38 | if(kind.name().equals(StandardWatchEventKinds.ENTRY_MODIFY.name()) 39 | && loader.match(event.context().toString())){ 40 | loader.reload(pathStr); 41 | break; 42 | } 43 | logger.debug("A "+kind+" example is detected on "+event.context().toString()); 44 | } 45 | boolean reset = key.reset(); 46 | if(!reset){ 47 | break; 48 | } 49 | } 50 | }catch(Exception e){ 51 | e.printStackTrace(); 52 | logger.error(e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/InternalServerHandler.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler; 2 | 3 | import core.entity.Frame; 4 | import core.netty.group.ServerChannelGroup; 5 | import core.netty.handler.processor.ProcessorManager; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import lombok.extern.slf4j.Slf4j; 9 | import server.internal.handler.processor.constant.ProcessorEnum; 10 | 11 | /** 12 | * @Author wneck130@gmail.com 13 | * @function 业务处理handler,所有协议命令在本类中处理 14 | */ 15 | @Slf4j 16 | public class InternalServerHandler extends SimpleChannelInboundHandler { 17 | 18 | @Override 19 | protected void channelRead0(ChannelHandlerContext ctx, Frame msg) throws Exception { 20 | byte cmd = msg.getCmd(); 21 | ProcessorManager.getInstance(ProcessorEnum.getClassByCmd(cmd)).request(ctx, msg); 22 | } 23 | 24 | @Override 25 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 26 | //internal channel创建时,维护internalGroup 27 | ServerChannelGroup.addInternal(ctx.channel()); 28 | log.debug("channel[{}]进入internalChannel组!!!", ctx.channel().id().asShortText()); 29 | } 30 | 31 | @Override 32 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 33 | //internal channel销毁时,维护internalGroup 34 | ServerChannelGroup.removeInternal(ctx.channel()); 35 | } 36 | 37 | /** 38 | * 通道异常触发 39 | * 40 | * @param ctx 41 | * @param cause 42 | * @throws Exception 43 | */ 44 | @Override 45 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 46 | log.error("ServerInternal[{}]发生异常:{}", ctx.channel().id(), cause.getStackTrace(), cause); 47 | ctx.close(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /assembly.xml: -------------------------------------------------------------------------------- 1 | 4 | package 5 | 6 | tar 7 | 8 | true 9 | 10 | 11 | ${basedir}/src/main/resources 12 | 13 | *.yml 14 | *.xml 15 | 16 | true 17 | config 18 | 19 | 20 | ${basedir}/src/main/resources 21 | 22 | *.yml 23 | *.xml 24 | 25 | true 26 | config 27 | 28 | 29 | src/main/resources/runScript 30 | ${file.separator}bin 31 | 32 | 33 | ${project.build.directory}/libs 34 | libs 35 | 36 | *.jar 37 | 38 | 39 | 40 | ${project.build.directory} 41 | ${file.separator} 42 | 43 | *.jar 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /nat-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | netty-nat 7 | com.scrat 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | nat-core 13 | 14 | 15 | 16 | org.apache.logging.log4j 17 | log4j-core 18 | 2.13.2 19 | 20 | 21 | org.apache.logging.log4j 22 | log4j-api 23 | 2.13.2 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | provided 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | 34 | 35 | io.netty 36 | netty-all 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | com.fasterxml.jackson.dataformat 46 | jackson-dataformat-yaml 47 | 2.12.5 48 | 49 | 50 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/proxy/handler/TcpInLogHandler.java: -------------------------------------------------------------------------------- 1 | package client.proxy.handler; 2 | 3 | import core.entity.Frame; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | /** 12 | * @author wneck130@gmail.com 13 | * @Description: 14 | * @date 2021/9/27 15 | */ 16 | @Slf4j 17 | public class TcpInLogHandler extends SimpleChannelInboundHandler { 18 | 19 | private String serverIP; 20 | 21 | private String serverPort; 22 | 23 | @Override 24 | protected void channelRead0(ChannelHandlerContext ctx, Frame msg) throws Exception { 25 | log.debug("TcpProxyClient收到消息:{}", msg.toString()); 26 | ctx.fireChannelRead(msg); 27 | } 28 | 29 | @Override 30 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 31 | InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress(); 32 | serverIP = inSocket.getAddress().getHostAddress(); 33 | serverPort = String.valueOf(inSocket.getPort()); 34 | log.debug("成功创建与服务{}:{}的连接,Channel[{}]", serverIP, serverPort, ctx.channel().id()); 35 | super.channelActive(ctx); 36 | } 37 | 38 | @Override 39 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 40 | log.debug("关闭与服务{}:{}的连接,成功回收Channel[{}]", serverIP, serverPort, ctx.channel().id()); 41 | super.channelInactive(ctx); 42 | } 43 | 44 | /** 45 | * 通道异常触发 46 | * @param ctx 47 | * @param cause 48 | * @throws Exception 49 | */ 50 | @Override 51 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 52 | Channel channel = ctx.channel(); 53 | if(!channel.isActive()){ 54 | ctx.close(); 55 | } 56 | log.error("TcpProxyClient连接[{}]发生异常:{}", ctx.channel().id(), cause.getStackTrace()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/proxy/handler/TcpInLogHandler.java: -------------------------------------------------------------------------------- 1 | package server.proxy.handler; 2 | 3 | import core.entity.Frame; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | /** 12 | * @author wneck130@gmail.com 13 | * @Description: 14 | * @date 2021/9/27 15 | */ 16 | @Slf4j 17 | public class TcpInLogHandler extends SimpleChannelInboundHandler { 18 | 19 | private String clientIP; 20 | 21 | private String clientPort; 22 | 23 | @Override 24 | protected void channelRead0(ChannelHandlerContext ctx, Frame msg) throws Exception { 25 | log.debug("TcpProxyServer收到消息:{}", msg.toString()); 26 | ctx.fireChannelRead(msg); 27 | } 28 | 29 | @Override 30 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 31 | InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress(); 32 | clientIP = insocket.getAddress().getHostAddress(); 33 | clientPort = String.valueOf(insocket.getPort()); 34 | log.debug("收到来自{}:{}的请求,成功创建Channel[{}]", clientIP, clientPort, ctx.channel().id()); 35 | super.channelActive(ctx); 36 | } 37 | 38 | @Override 39 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 40 | log.debug("关闭来自{}:{}的请求,成功回收Channel[{}]", clientIP, clientPort, ctx.channel().id()); 41 | super.channelInactive(ctx); 42 | } 43 | 44 | /** 45 | * 通道异常触发 46 | * @param ctx 47 | * @param cause 48 | * @throws Exception 49 | */ 50 | @Override 51 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 52 | Channel channel = ctx.channel(); 53 | if(!channel.isActive()){ 54 | ctx.close(); 55 | } 56 | log.error("TcpProxyServer连接[{}]发生异常:{}", ctx.channel().id(), cause.getStackTrace()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/client/BaseClient.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.client; 2 | 3 | import core.properties.cache.PropertiesCache; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.concurrent.*; 11 | 12 | /** 13 | * @author wneck130@gmail.com 14 | * @function 客户端基类 15 | */ 16 | @Slf4j 17 | @Getter 18 | @Setter 19 | public class BaseClient { 20 | protected EventLoopGroup group; 21 | protected PropertiesCache cache; 22 | private static int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1; 23 | private static int maximumPoolSize = Runtime.getRuntime().availableProcessors() * 3; 24 | private static int keepAliveTime = 30; 25 | public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, 26 | maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), 27 | new ThreadPoolExecutor.DiscardOldestPolicy()); 28 | 29 | public static ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); 30 | 31 | public ChannelFuture future; 32 | 33 | protected String host; 34 | protected Integer port; 35 | protected void doShutdown(){ 36 | try{ 37 | if(group != null){ 38 | group.shutdownGracefully().sync(); 39 | } 40 | 41 | if(threadPoolExecutor != null){ 42 | threadPoolExecutor.shutdownNow(); 43 | } 44 | log.debug("BaseClient has been shutdown gracefully!"); 45 | }catch(Exception ex){ 46 | log.error("Error when shutdown client!!!"); 47 | } 48 | } 49 | 50 | protected void addShutdownHook() { 51 | Runtime runtime = Runtime.getRuntime(); 52 | runtime.addShutdownHook(new Thread(() -> { 53 | log.debug("执行ShutdownHook..."); 54 | doShutdown(); 55 | })); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/proxy/ProxyNettyClient.java: -------------------------------------------------------------------------------- 1 | package client.proxy; 2 | 3 | import client.proxy.handler.ProxyClientHandler; 4 | import client.proxy.handler.TcpInLogHandler; 5 | import client.proxy.handler.TcpOutLogHandler; 6 | import core.netty.stater.client.BaseClient; 7 | import core.netty.stater.client.NettyClient; 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.ChannelInitializer; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * @author wneck130@gmail.com 16 | * @Description: 17 | * @date 2021/10/8 18 | */ 19 | @Slf4j 20 | public class ProxyNettyClient extends BaseClient implements NettyClient{ 21 | 22 | @Override 23 | public void init() { 24 | } 25 | 26 | @Override 27 | public void start() { 28 | try { 29 | //通过Bootstrap启动服务端 30 | Bootstrap client = new Bootstrap(); 31 | //定义线程组,处理读写和链接事件 32 | group = new NioEventLoopGroup(); 33 | client.group(group) 34 | .channel(NioSocketChannel.class) 35 | .handler(new ChannelInitializer() { 36 | @Override 37 | protected void initChannel(NioSocketChannel ch) { 38 | //加入自定义的handler 39 | ch.pipeline() 40 | //inbound流日志记录 41 | .addLast(new TcpInLogHandler()) 42 | //outbound流日志记录 43 | .addLast(new TcpOutLogHandler()) 44 | .addLast(new ProxyClientHandler()); 45 | } 46 | }); 47 | log.debug("启动ProxyClient连接到{}:{}", host, port); 48 | future = client.connect(host, port).sync(); 49 | } catch (Exception e) { 50 | log.error("启动ProxyClient连接到失败"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/decoder/PojoToByteEncoder.java: -------------------------------------------------------------------------------- 1 | package server.internal.decoder; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.utils.BufUtil; 6 | import core.utils.ByteUtil; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.codec.MessageToByteEncoder; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author wneck130@gmail.com 18 | * @function 编码器,将POJO对象转化成字节流 19 | */ 20 | @Slf4j 21 | public class PojoToByteEncoder extends MessageToByteEncoder { 22 | 23 | @Override 24 | protected void encode(ChannelHandlerContext ctx, Frame msg, ByteBuf out) throws Exception { 25 | out.writeByte(msg.getPv()); 26 | out.writeLong(msg.getSerial()); 27 | out.writeBytes(msg.getReq().getBytes(StandardCharsets.UTF_8)); 28 | out.writeBytes(msg.getRes().getBytes(StandardCharsets.UTF_8)); 29 | out.writeByte(msg.getCmd()); 30 | if (msg.getData() == null) { 31 | out.writeInt(0); 32 | } else { 33 | //填充默认值 34 | out.writeInt(0); 35 | LinkedHashMap dataMap = msg.getData(); 36 | int length = dataMap.entrySet().stream().map(Map.Entry::getValue).mapToInt(value -> { 37 | if (value instanceof Byte) { 38 | out.writeByte((byte) value); 39 | return 1; 40 | } else if (value instanceof byte[]) { 41 | out.writeBytes((byte[]) value); 42 | return ((byte[]) value).length; 43 | } else { 44 | //TODO 待实现多种数据类型的字节转化 45 | return 0; 46 | } 47 | }).sum(); 48 | out.setBytes(FrameConstant.FRAME_LEN_INDEX, ByteUtil.fromInt(length)); 49 | log.debug("发送消息:{}", ByteUtil.toHexString(BufUtil.getArray(out))); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/decoder/PojoToByteEncoder.java: -------------------------------------------------------------------------------- 1 | package client.internal.decoder; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.utils.BufUtil; 6 | import core.utils.ByteUtil; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.codec.MessageToByteEncoder; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author wneck130@gmail.com 18 | * @function POJO对象编码器,将POJO对象转化成字节流 19 | */ 20 | @Slf4j 21 | public class PojoToByteEncoder extends MessageToByteEncoder { 22 | 23 | @Override 24 | protected void encode(ChannelHandlerContext ctx, Frame msg, ByteBuf out) throws Exception { 25 | out.writeByte(msg.getPv()); 26 | out.writeLong(msg.getSerial()); 27 | out.writeBytes(msg.getReq().getBytes(StandardCharsets.UTF_8)); 28 | out.writeBytes(msg.getRes().getBytes(StandardCharsets.UTF_8)); 29 | out.writeByte(msg.getCmd()); 30 | if (msg.getData() == null) { 31 | out.writeInt(0); 32 | } else { 33 | //填充默认值 34 | out.writeInt(0); 35 | LinkedHashMap dataMap = msg.getData(); 36 | int length = dataMap.entrySet().stream().map(Map.Entry::getValue).mapToInt(value -> { 37 | if (value instanceof Byte) { 38 | out.writeByte((byte) value); 39 | return 1; 40 | } else if (value instanceof byte[]) { 41 | out.writeBytes((byte[]) value); 42 | return ((byte[]) value).length; 43 | } else { 44 | //TODO 待实现多种数据类型的字节转化 45 | return 0; 46 | } 47 | }).sum(); 48 | out.setBytes(FrameConstant.FRAME_LEN_INDEX, ByteUtil.fromInt(length)); 49 | log.debug("发送消息:{}", ByteUtil.toHexString(BufUtil.getArray(out))); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/HeartbeatProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.netty.handler.processor.Processor; 6 | import core.utils.BufUtil; 7 | import core.utils.ByteUtil; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import lombok.extern.slf4j.Slf4j; 11 | import server.internal.handler.processor.constant.ProcessorEnum; 12 | 13 | import java.util.Objects; 14 | import java.util.UnknownFormatConversionException; 15 | 16 | /** 17 | * @Author wneck130@gmail.com 18 | * @Function 心跳命令处理器(cmd:0x02),命令由internalClient发起请求,internalServer回复响应 19 | */ 20 | @Slf4j 21 | public class HeartbeatProcessor implements Processor { 22 | 23 | /** 24 | * heartbeat数据帧处理 25 | * @param in netty获取的TCP数据流 26 | * @return 27 | * @throws Exception 28 | */ 29 | @Override 30 | public Frame assemble(ByteBuf in) throws Exception { 31 | try { 32 | //解析协议公共部分 33 | return new Frame().quickHead(in); 34 | } catch (Exception e) { 35 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 36 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 37 | } 38 | } 39 | 40 | /** 41 | * 处理heartbeat业务 42 | * @param ctx netty channel上下文 43 | * @param msg 解析成Frame结构的TCP请求数据帧 44 | * @throws Exception 45 | */ 46 | @Override 47 | public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 48 | //保持连接活性,回复心跳 49 | Frame response = new Frame(); 50 | response.setPv(FrameConstant.RES_PV); 51 | response.setSerial(msg.getSerial()); 52 | response.setReq(msg.getReq()); 53 | response.setRes(ctx.channel().id().asShortText()); 54 | response.setCmd(msg.getCmd()); 55 | ctx.writeAndFlush(response); 56 | } 57 | 58 | /** 59 | * processor对应的命令字适配 60 | * @param cmd 61 | * @return 62 | */ 63 | @Override 64 | public boolean supply(byte cmd) { 65 | return Objects.equals(ProcessorEnum.HEARTBEAT.getCmd(), cmd); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/utils/BufUtil.java: -------------------------------------------------------------------------------- 1 | package core.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | /** 6 | * 7 | * @author wneck130@gmail.com 8 | */ 9 | public final class BufUtil { 10 | 11 | /** 12 | * Note: Only use this method when it's necessary 13 | * @param buf 14 | * @return 15 | */ 16 | public static byte[] getArray(ByteBuf buf){ 17 | byte[] result = null; 18 | if(buf.hasArray()){ 19 | result = buf.array(); 20 | }else{ 21 | result = new byte[buf.readableBytes()]; 22 | buf.getBytes(buf.readerIndex(), result); 23 | } 24 | return result; 25 | } 26 | 27 | /** 28 | * XOR value of all bytes in a ByteBuf 29 | * @param buf 30 | * @return 31 | */ 32 | public static byte xor(ByteBuf buf){ 33 | byte result = 0x00; 34 | int len = buf.readableBytes(); 35 | for(int i=0; i data = new LinkedHashMap<>(); 30 | 31 | @Override 32 | public String toString() { 33 | StringBuilder sb = new StringBuilder(); 34 | sb.append("cmd:"+ ByteUtil.toHexString(cmd) + "\n"); 35 | sb.append("serial:"+ serial + "\n"); 36 | sb.append("req:"+ req + "\n"); 37 | sb.append("res:"+ res + "\n"); 38 | if (!data.isEmpty()) { 39 | data.forEach((key, value) -> { 40 | if (value instanceof Byte) { 41 | sb.append(key + ByteUtil.toHexString((byte)value) + "\n"); 42 | } else if (value instanceof Byte[]) { 43 | sb.append(key + ByteUtil.toHexString((byte[])value) + "\n"); 44 | } else { 45 | sb.append(key + value.toString() + "\n"); 46 | } 47 | }); 48 | } 49 | return sb.toString(); 50 | } 51 | 52 | /** 53 | * 快速解析公共协议部分的内容 54 | * @param in 55 | */ 56 | public Frame quickHead(ByteBuf in) { 57 | //解析协议公共部分 58 | byte pv = in.readByte(); 59 | long serial = in.readLong(); 60 | byte[] reqBytes = new byte[8]; 61 | in.readBytes(reqBytes); 62 | String req = new String(reqBytes, StandardCharsets.UTF_8); 63 | byte[] resBytes = new byte[8]; 64 | in.readBytes(resBytes); 65 | String res = new String(resBytes, StandardCharsets.UTF_8); 66 | byte cmd = in.readByte(); 67 | int len = in.readInt(); 68 | this.setPv(pv); 69 | this.setSerial(serial); 70 | this.setReq(req); 71 | this.setRes(res); 72 | this.setCmd(cmd); 73 | this.setLen(len); 74 | return this; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/LoginProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | import client.internal.handler.processor.constant.ProcessorEnum; 4 | import core.constant.FrameConstant; 5 | import core.entity.Frame; 6 | import core.netty.group.ServerChannelGroup; 7 | import core.netty.group.channel.message.ResponseEvent; 8 | import core.netty.handler.processor.Processor; 9 | import core.utils.BufUtil; 10 | import core.utils.ByteUtil; 11 | import io.netty.buffer.ByteBuf; 12 | import io.netty.channel.Channel; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | import java.util.LinkedHashMap; 16 | import java.util.Objects; 17 | import java.util.UnknownFormatConversionException; 18 | 19 | /** 20 | * @Author wneck130@gmail.com 21 | * @Function 接入命令处理器(cmd:0x01),命令由internalClient发起请求,internalServer回复响应 22 | */ 23 | @Slf4j 24 | public class LoginProcessor implements Processor { 25 | 26 | @Override 27 | public Frame assemble(ByteBuf in) throws Exception { 28 | try { 29 | //解析协议公共部分 30 | Frame frame = new Frame().quickHead(in); 31 | //解析Data部分 32 | byte result = in.readByte(); 33 | LinkedHashMap dataMap = new LinkedHashMap<>(1); 34 | dataMap.put("result", result); 35 | frame.setData(dataMap); 36 | return frame; 37 | } catch (Exception e) { 38 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 39 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 40 | } 41 | } 42 | 43 | @Override 44 | public void response(ResponseEvent responseEvent) { 45 | byte result = (byte)responseEvent.getResponse().getData().get("result"); 46 | if (result == FrameConstant.RESULT_FAIL) { 47 | log.error("接入失败!!!"); 48 | } 49 | } 50 | 51 | @Override 52 | public void timeout(ResponseEvent responseEvent) { 53 | try { 54 | //超时后默认进行一次重试 55 | Channel internalChannel = ServerChannelGroup.forkChannel(responseEvent.getChannelId()); 56 | internalChannel.writeAndFlush(responseEvent.getRequest()); 57 | } catch (Exception e) { 58 | log.error("channel[{}]处理超时指令CMD:{},SERIAL:{}重试失败", responseEvent.getChannelId(), 59 | responseEvent.getRequest().getCmd(), responseEvent.getRequest().getSerial()); 60 | } 61 | } 62 | 63 | @Override 64 | public boolean supply(byte cmd) { 65 | return Objects.equals(ProcessorEnum.LOGIN.getCmd(), cmd); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netty-nat ![image](https://img.shields.io/github/v/release/Likedan130/netty-nat.svg) 2 | 3 | 基于netty的TCP/Http请求转发代理程序 4 | 5 | ## 简介 6 |  在特定的网络环境或安全审计要求下,我们可能会面临网络被限定为单向访问的情况,本工具可以实现在单向网络中设置代理从而实现双向访问的目的。可简单用作内网穿透工具。也可以配置成LVS负载均衡服务 7 | 8 | ## 依赖/知识准备 9 | - netty-高性能NIO通信框架 10 | - tcp/ip通信基础知识 11 | 12 | ## 工具原理说明 13 | 1. [通信协议](doc/代理程序通信协议.docx) 14 | ----- 15 | 2. 程序运行时序图![image](doc/netty-nat%E6%97%B6%E5%BA%8F%E5%9B%BE.png) 16 | ----- 17 | 3. 网络拓扑图![image](doc/netty-nat%E7%BD%91%E8%B7%AF%E6%8B%93%E6%89%91%E5%9B%BE.png) 18 | 19 | ## 使用方式 20 | 1. **将项目克隆到本地** 21 | 2. **修改配置文件** 22 | 项目包含两个独立配置文件,分别为 23 |  netty-nat 24 |  |--netty-client 25 |  |----properties.yml 26 |  |--netty-server 27 |  |----properties.yml 28 | 29 | - client对应properties配置: 30 | 31 | ```yaml 32 | #内部连接池大小,内部连接只需转发外部与被代理服务间业务数据,可重用通道 33 | internal: 34 | channel: 35 | init: 36 | num: 10 37 | #服务端ip和端口 38 | server: 39 | host: 127.0.0.1 40 | port: 8083 41 | #隧道信息,一条完整的外部>>服务端>>客户端>>被代理服务间的通路称为隧道 42 | tunnel: 43 | #tunnel示例,代理本地的mysql数据库服务和nacos服务 44 | #服务端监听端口 45 | - serverPort: 9000 46 | #客户端连接的被代理服务端口 47 | clientPort: 3306 48 | #客户端连接的被代理服务端口 49 | clientHost: 127.0.0.1 50 | - serverPort: 9001 51 | clientPort: 8848 52 | clientHost: 127.0.0.1 53 | #接入请求的接入密码 54 | password: '123456' 55 | ``` 56 | 57 | - server对应properties配置: 58 | 59 | ```yaml 60 | #内部通信使用的端口,需要与客户端的internal.server.port值保持一致 61 | internal: 62 | server: 63 | port: 8083 64 | #接入请求的接入密码 65 | password: '123456' 66 | ``` 67 | 3. **打包** 68 | 项目中使用maven管理第三方依赖,打包使用maven-jar-plugin,自定义打包行为定义在项目根目录的assembly.xml中,打包时执行: 69 | ```mvn clean package -Dmaven.test.skip=true``` 70 | 4. **部署** 71 | 项目打包后获得 项目名称-版本号.zip 72 | 解压后获得 73 | 74 | - 项目主运行jar 项目名称-版本号.jar 75 | - 项目第三方包依赖目录 libs 76 | - 项目配置文件目录 config 77 | 将解压后文件及目录保持当前层级关系上传至服务器 78 | 5. **启动** 79 | - 直接运行jar 80 | 调整resources目录下的log4j2.xml和properties.yml为当前服务器相关配置,执行打包编译流程,获得target目录下的tar文件并解压 81 | 运行```java -jar nat-server-1.0-SNAPSHOT.jar & ```启动服务端 82 | 观察到 InternalServer started on port xxxx......即表示服务启动成功 83 | 运行```java -jar nat-client-1.0-SNAPSHOT.jar & ```启动客户端 84 | - docker启动 85 | 调整resources目录下的log4j2.xml和properties.yml为当前服务器相关配置,执行打包编译流程 86 | 命令行执行docker-compose up -d,在docker中确认容器的运行情况 87 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/DownStreamProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | import core.netty.group.ClientChannelGroup; 4 | import client.internal.handler.processor.constant.ProcessorEnum; 5 | import core.constant.FrameConstant; 6 | import core.entity.Frame; 7 | import core.netty.handler.processor.Processor; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.Unpooled; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelHandlerContext; 12 | 13 | import java.util.LinkedHashMap; 14 | import java.util.Objects; 15 | 16 | /** 17 | * @author wneck130@gmail.com 18 | * @Description: 数据下发处理器(cmd:0xEE),由proxyServer发起请求,proxyClient回复响应 19 | * @date 2021/10/8 20 | */ 21 | public class DownStreamProcessor implements Processor { 22 | 23 | @Override 24 | public Frame assemble(ByteBuf in) throws Exception { 25 | Frame frame = new Frame().quickHead(in); 26 | byte[] dataBytes = new byte[in.readableBytes()]; 27 | in.readBytes(dataBytes); 28 | LinkedHashMap dataMap = new LinkedHashMap<>(); 29 | dataMap.put("data", dataBytes); 30 | frame.setData(dataMap); 31 | return frame; 32 | } 33 | 34 | @Override 35 | public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 36 | //转发来自服务端的数据 37 | String clientChannelId = msg.getRes(); 38 | Channel proxyClientChannel = ClientChannelGroup.findProxy(clientChannelId); 39 | ByteBuf downStream = Unpooled.buffer(); 40 | downStream.writeBytes((byte[]) msg.getData().get("data")); 41 | proxyClientChannel.writeAndFlush(downStream).addListener(future -> { 42 | Frame response = new Frame(); 43 | response.setPv(FrameConstant.RES_PV); 44 | response.setSerial(msg.getSerial()); 45 | response.setReq(msg.getReq()); 46 | response.setRes(msg.getRes()); 47 | response.setCmd(msg.getCmd()); 48 | response.setLen(1); 49 | LinkedHashMap dataMap = new LinkedHashMap<>(); 50 | if (future.isSuccess()) { 51 | dataMap.put("result", FrameConstant.RESULT_SUCCESS); 52 | } else { 53 | dataMap.put("result", FrameConstant.RESULT_FAIL); 54 | } 55 | response.setData(dataMap); 56 | ctx.writeAndFlush(response); 57 | }); 58 | } 59 | 60 | @Override 61 | public boolean supply(byte cmd) { 62 | return Objects.equals(ProcessorEnum.DOWN_STREAM.getCmd(), cmd); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/InternalClientHandler.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler; 2 | 3 | import core.netty.group.ClientChannelGroup; 4 | import client.internal.handler.processor.constant.ProcessorEnum; 5 | import client.internal.InternalNettyClient; 6 | import core.entity.Frame; 7 | import core.netty.group.channel.message.MessageContext; 8 | import core.netty.handler.processor.ProcessorManager; 9 | import core.properties.cache.PropertiesCache; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.channel.SimpleChannelInboundHandler; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import java.util.Arrays; 15 | 16 | /** 17 | * @Author wneck130@gmail.com 18 | * @function 业务处理handler,所有协议命令在本类中处理 19 | */ 20 | @Slf4j 21 | public class InternalClientHandler extends SimpleChannelInboundHandler { 22 | @Override 23 | protected void channelRead0(ChannelHandlerContext ctx, Frame msg) throws Exception { 24 | byte cmd = msg.getCmd(); 25 | ProcessorManager.getInstance(ProcessorEnum.getClassByCmd(cmd)).request(ctx, msg); 26 | } 27 | 28 | @Override 29 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 30 | if (ClientChannelGroup.internalGroup.size() > PropertiesCache.getInstance().getInt(InternalNettyClient.INIT_NUM)) { 31 | ctx.close(); 32 | return; 33 | } 34 | MessageContext messageContext = MessageContext.getInstance(); 35 | //将所有指令的处理器都指派给messageContext作为监听器 36 | Arrays.stream(ProcessorEnum.values()).forEach(processorEnum -> { 37 | messageContext.addResponseListener(ProcessorManager.getInstance(processorEnum.getClazz())); 38 | }); 39 | ClientChannelGroup.addInternal(ctx.channel()); 40 | } 41 | 42 | @Override 43 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 44 | ClientChannelGroup.removeInternal(ctx.channel()); 45 | log.debug("[internalClient:{}]断开连接", ctx.channel().id().asShortText()); 46 | InternalNettyClient internalClient = new InternalNettyClient(); 47 | if (ClientChannelGroup.internalGroup.size() < PropertiesCache.getInstance().getInt(InternalNettyClient.INIT_NUM)) { 48 | internalClient.start(); 49 | } 50 | 51 | } 52 | 53 | /** 54 | * 通道异常触发 55 | * @param ctx 56 | * @param cause 57 | * @throws Exception 58 | */ 59 | @Override 60 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 61 | log.error("ClientInternal[{}]发生异常:{}", ctx.channel().id(), cause.getStackTrace()); 62 | ctx.close(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/stater/server/BaseServer.java: -------------------------------------------------------------------------------- 1 | package core.netty.stater.server; 2 | 3 | import core.properties.cache.PropertiesCache; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.util.concurrent.GenericFutureListener; 7 | import lombok.Getter; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.concurrent.*; 11 | 12 | /** 13 | * @Author wneck130@gmail.com 14 | * @Function 服务端基类 15 | */ 16 | @Slf4j 17 | @Getter 18 | public abstract class BaseServer { 19 | /** 20 | * 全局异步任务线程池 21 | */ 22 | private static int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1; 23 | private static int maximumPoolSize = Runtime.getRuntime().availableProcessors() * 3; 24 | private static int keepAliveTime = 10; 25 | public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, 26 | maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), 27 | new ThreadPoolExecutor.DiscardOldestPolicy()); 28 | 29 | /** 30 | * boss线程组 31 | */ 32 | protected EventLoopGroup bossGroup; 33 | /** 34 | * worker线程组 35 | */ 36 | protected EventLoopGroup workerGroup; 37 | /** 38 | * NioServerSocketChannel对应的future 39 | */ 40 | protected ChannelFuture nioServerFuture; 41 | 42 | /** 43 | * 通道读写超时配置项 44 | */ 45 | protected final int READ_IDLE = 0; 46 | protected final int WRITE_IDLE = 0; 47 | protected final int ALL_IDLE = 10; 48 | 49 | /** 50 | * 代理程序对外提供的服务端口 51 | */ 52 | protected static String PORT = "proxy.server.port"; 53 | 54 | protected int port; 55 | 56 | protected GenericFutureListener genericFutureListener; 57 | 58 | protected PropertiesCache cache; 59 | 60 | protected void doShutdown() { 61 | try { 62 | if (bossGroup != null) { 63 | bossGroup.shutdownGracefully().sync(); 64 | } 65 | if (workerGroup != null) { 66 | workerGroup.shutdownGracefully().sync(); 67 | } 68 | 69 | if (threadPoolExecutor != null) { 70 | threadPoolExecutor.shutdownNow(); 71 | } 72 | log.debug("BaseServer has been shutdown gracefully!"); 73 | } catch (Exception ex) { 74 | log.debug("Error when shutdown server!!!"); 75 | } 76 | } 77 | 78 | protected void addShutdownHook() { 79 | Runtime runtime = Runtime.getRuntime(); 80 | runtime.addShutdownHook(new Thread() { 81 | @Override 82 | public void run() { 83 | log.info("执行 addShutdownHook..."); 84 | doShutdown(); 85 | close(); 86 | } 87 | }); 88 | } 89 | 90 | public abstract void close(); 91 | } 92 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/PreConnectProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | import core.netty.group.channel.message.ResponseEvent; 4 | import core.constant.FrameConstant; 5 | import core.entity.Frame; 6 | import core.netty.handler.processor.Processor; 7 | import core.netty.group.ServerChannelGroup; 8 | import core.utils.BufUtil; 9 | import core.utils.ByteUtil; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.channel.Channel; 12 | import lombok.extern.slf4j.Slf4j; 13 | import server.internal.handler.processor.constant.ProcessorEnum; 14 | 15 | import java.util.LinkedHashMap; 16 | import java.util.Objects; 17 | import java.util.UnknownFormatConversionException; 18 | 19 | /** 20 | * @author wneck130@gmail.com 21 | * @Description: 预创建连接命令处理器(cmd:0x03),命令由proxyServer发起请求,proxyClient回复响应 22 | * @date 2021/9/27 23 | */ 24 | @Slf4j 25 | public class PreConnectProcessor implements Processor { 26 | 27 | /** 28 | * preConnect数据帧组装,服务端处理的是响应命令 29 | * @param in netty获取的TCP数据流 30 | * @return 31 | * @throws Exception 32 | */ 33 | @Override 34 | public Frame assemble(ByteBuf in) throws Exception { 35 | try { 36 | //解析协议公共部分 37 | Frame frame = new Frame().quickHead(in); 38 | byte result = in.readByte(); 39 | LinkedHashMap dataMap = new LinkedHashMap<>(1); 40 | dataMap.put("result", result); 41 | frame.setData(dataMap); 42 | return frame; 43 | } catch (Exception e) { 44 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 45 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 46 | } 47 | } 48 | 49 | /** 50 | * 处理预创建连接的响应消息 51 | * @param responseEvent 52 | */ 53 | @Override 54 | public void response(ResponseEvent responseEvent) { 55 | //处理响应信息 56 | byte result = (byte)responseEvent.getResponse().getData().get("result"); 57 | if (result == FrameConstant.RESULT_SUCCESS) { 58 | //成功后缓存配对关系 59 | ServerChannelGroup.addChannelPair(responseEvent.getResponse().getReq(), 60 | responseEvent.getResponse().getRes()); 61 | } 62 | } 63 | 64 | @Override 65 | public void timeout(ResponseEvent responseEvent) { 66 | try { 67 | //超时后默认进行一次重试 68 | Channel internalChannel = ServerChannelGroup.forkChannel(responseEvent.getChannelId()); 69 | internalChannel.writeAndFlush(responseEvent.getRequest()); 70 | } catch (Exception e) { 71 | log.error("channel[{}]处理超时指令CMD:{},SERIAL:{}重试失败", responseEvent.getChannelId(), 72 | responseEvent.getRequest().getCmd(), responseEvent.getRequest().getSerial()); 73 | } 74 | } 75 | 76 | @Override 77 | public boolean supply(byte cmd) { 78 | return Objects.equals(ProcessorEnum.PRE_CONNECT.getCmd(), cmd); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/UpStreamProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | import client.internal.handler.processor.constant.ProcessorEnum; 4 | import core.constant.FrameConstant; 5 | import core.entity.Frame; 6 | import core.netty.group.ServerChannelGroup; 7 | import core.netty.group.channel.message.ResponseEvent; 8 | import core.netty.handler.processor.Processor; 9 | import core.utils.BufUtil; 10 | import core.utils.ByteUtil; 11 | import io.netty.buffer.ByteBuf; 12 | import io.netty.channel.Channel; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | import java.util.LinkedHashMap; 16 | import java.util.Objects; 17 | import java.util.UnknownFormatConversionException; 18 | 19 | /** 20 | * @Author wneck130@gmail.com 21 | * @function 数据上传处理器(cmd:0xFF),命令由proxyClient发起请求,internalClient处理响应 22 | */ 23 | @Slf4j 24 | public class UpStreamProcessor implements Processor { 25 | 26 | @Override 27 | public Frame assemble(ByteBuf in) throws Exception { 28 | try { 29 | Frame frame = new Frame().quickHead(in); 30 | //解析协议data部分 31 | byte result = in.readByte(); 32 | LinkedHashMap dataMap = new LinkedHashMap<>(); 33 | dataMap.put("result", result); 34 | frame.setData(dataMap); 35 | return frame; 36 | } catch (Exception e) { 37 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 38 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 39 | } 40 | } 41 | 42 | @Override 43 | public void response(ResponseEvent responseEvent) { 44 | try { 45 | LinkedHashMap dataMap = responseEvent.getResponse().getData(); 46 | byte result = (byte) dataMap.get("result"); 47 | if (result == FrameConstant.RESULT_FAIL) { 48 | Channel internalChannel = ServerChannelGroup.forkChannel(responseEvent.getChannelId()); 49 | internalChannel.writeAndFlush(responseEvent.getRequest()); 50 | } 51 | } catch (Exception e) { 52 | log.error("channel[{}]处理响应CMD:{},SERIAL:{}重试失败", responseEvent.getChannelId(), 53 | responseEvent.getRequest().getCmd(), responseEvent.getRequest().getSerial()); 54 | } 55 | } 56 | 57 | @Override 58 | public void timeout(ResponseEvent responseEvent) { 59 | try { 60 | //超时后默认进行一次重试 61 | Channel internalChannel = ServerChannelGroup.forkChannel(responseEvent.getChannelId()); 62 | internalChannel.writeAndFlush(responseEvent.getRequest()); 63 | } catch (Exception e) { 64 | log.error("channel[{}]处理超时指令CMD:{},SERIAL:{}重试失败", responseEvent.getChannelId(), 65 | responseEvent.getRequest().getCmd(), responseEvent.getRequest().getSerial(), e); 66 | } 67 | } 68 | 69 | @Override 70 | public boolean supply(byte cmd) { 71 | return Objects.equals(ProcessorEnum.UP_STREAM.getCmd(), cmd); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/UpStreamProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.netty.group.ServerChannelGroup; 6 | import core.netty.handler.processor.Processor; 7 | import core.utils.BufUtil; 8 | import core.utils.ByteUtil; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.buffer.Unpooled; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import lombok.extern.slf4j.Slf4j; 14 | import server.internal.handler.processor.constant.ProcessorEnum; 15 | 16 | import java.util.LinkedHashMap; 17 | import java.util.Objects; 18 | import java.util.UnknownFormatConversionException; 19 | 20 | /** 21 | * @author wneck130@gmail.com 22 | * @Description: 上行数据处理器(cmd:0xFF),命令由proxyClient发起请求,proxyServer回复响应 23 | * @date 2021/9/29 24 | */ 25 | @Slf4j 26 | public class UpStreamProcessor implements Processor { 27 | 28 | @Override 29 | public Frame assemble(ByteBuf in) throws Exception { 30 | try { 31 | Frame frame = new Frame().quickHead(in); 32 | //解析协议data部分 33 | byte[] dataBytes = new byte[in.readableBytes()]; 34 | in.readBytes(dataBytes); 35 | //生成数据帧 36 | LinkedHashMap dataMap = new LinkedHashMap<>(); 37 | dataMap.put("data", dataBytes); 38 | frame.setData(dataMap); 39 | return frame; 40 | } catch (Exception e) { 41 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 42 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 43 | } 44 | } 45 | 46 | @Override 47 | public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 48 | //将消息转发给对应的外部请求方 49 | Channel proxyChannel = ServerChannelGroup.findProxy(msg.getRes()); 50 | ByteBuf upStream = Unpooled.buffer(); 51 | upStream.writeBytes((byte[])msg.getData().get("data")); 52 | proxyChannel.writeAndFlush(upStream).addListener(future -> { 53 | //根据发送结果回写响应 54 | Frame response = new Frame(); 55 | response.setPv(FrameConstant.RES_PV); 56 | response.setSerial(msg.getSerial()); 57 | response.setCmd(msg.getCmd()); 58 | response.setLen(FrameConstant.FRAME_RESULT_LEN); 59 | LinkedHashMap dataMap = new LinkedHashMap<>(1); 60 | proxyChannel.writeAndFlush(response); 61 | if (future.isSuccess()) { 62 | dataMap.put("result", FrameConstant.RESULT_SUCCESS); 63 | } else { 64 | log.error("向外部请求发送数据失败:", future.cause()); 65 | dataMap.put("result", FrameConstant.RESULT_FAIL); 66 | } 67 | response.setData(dataMap); 68 | Channel internalChannel = ServerChannelGroup.forkChannel(proxyChannel.id().asShortText()); 69 | internalChannel.writeAndFlush(response); 70 | }); 71 | } 72 | 73 | @Override 74 | public boolean supply(byte cmd) { 75 | return Objects.equals(ProcessorEnum.UP_STREAM.getCmd(), cmd); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/DownStreamProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.netty.group.ServerChannelGroup; 6 | import core.netty.group.channel.message.ResponseEvent; 7 | import core.netty.handler.processor.Processor; 8 | import core.utils.BufUtil; 9 | import core.utils.ByteUtil; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.channel.Channel; 12 | import lombok.extern.slf4j.Slf4j; 13 | import server.internal.handler.processor.constant.ProcessorEnum; 14 | 15 | import java.util.LinkedHashMap; 16 | import java.util.Objects; 17 | import java.util.UnknownFormatConversionException; 18 | 19 | /** 20 | * @Author wneck130@gmail.com 21 | * @function 数据下发处理器(cmd:0xEE),由proxyServer发起请求,proxyClient回复响应 22 | */ 23 | @Slf4j 24 | public class DownStreamProcessor implements Processor { 25 | 26 | /** 27 | * downStream数据帧处理,在服务端,处理的是downStream命令的响应 28 | * @param in netty获取的TCP数据流 29 | * @return 30 | * @throws Exception 31 | */ 32 | @Override 33 | public Frame assemble(ByteBuf in) throws Exception { 34 | try { 35 | //解析协议公共部分 36 | Frame frame = new Frame().quickHead(in); 37 | //解析协议data部分 38 | byte result = in.readByte(); 39 | //生成数据帧 40 | LinkedHashMap dataMap = new LinkedHashMap<>(); 41 | dataMap.put("result", result); 42 | frame.setData(dataMap); 43 | return frame; 44 | } catch (Exception e) { 45 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 46 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 47 | } 48 | } 49 | 50 | @Override 51 | public void response(ResponseEvent responseEvent) { 52 | try { 53 | byte result = (byte)responseEvent.getResponse().getData().get("result"); 54 | if (result != FrameConstant.RESULT_SUCCESS) { 55 | log.error("来自{}的消息收到客户端处理失败响应,尝试重发!!!", responseEvent.getChannelId()); 56 | Channel internalChannel = ServerChannelGroup.forkChannel(responseEvent.getChannelId()); 57 | internalChannel.writeAndFlush(responseEvent.getRequest()); 58 | } 59 | } catch (Exception e) { 60 | log.error("channel[{}]处理响应指令CMD:{},SERIAL:{}重试失败", responseEvent.getChannelId(), 61 | responseEvent.getRequest().getCmd(), responseEvent.getRequest().getSerial()); 62 | } 63 | } 64 | 65 | @Override 66 | public void timeout(ResponseEvent responseEvent) { 67 | try { 68 | //超时后默认进行一次重试 69 | Channel internalChannel = ServerChannelGroup.forkChannel(responseEvent.getChannelId()); 70 | internalChannel.writeAndFlush(responseEvent.getRequest()); 71 | } catch (Exception e) { 72 | log.error("channel[{}]处理超时指令CMD:{},SERIAL:{}重试失败", responseEvent.getChannelId(), 73 | responseEvent.getRequest().getCmd(), responseEvent.getRequest().getSerial()); 74 | } 75 | } 76 | 77 | /** 78 | * processor对应的命令字适配 79 | * @param cmd 80 | * @return 81 | */ 82 | @Override 83 | public boolean supply(byte cmd) { 84 | return Objects.equals(ProcessorEnum.DOWN_STREAM.getCmd(), cmd); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/proxy/handler/ProxyClientHandler.java: -------------------------------------------------------------------------------- 1 | package client.proxy.handler; 2 | 3 | import core.netty.group.ClientChannelGroup; 4 | import client.internal.handler.processor.constant.ProcessorEnum; 5 | import core.entity.Frame; 6 | import core.utils.BufUtil; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.SimpleChannelInboundHandler; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.util.LinkedHashMap; 14 | 15 | /** 16 | * @Author wneck130@gmail.com 17 | * @function 代理客户端业务handler,在收到被代理服务的消息时将其转发给服务端 18 | */ 19 | @Slf4j 20 | public class ProxyClientHandler extends SimpleChannelInboundHandler { 21 | @Override 22 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 23 | String clientChannelId = ctx.channel().id().asShortText(); 24 | String serverChannelId = ClientChannelGroup.getServerChannel(clientChannelId); 25 | //没有对应的服务端channel,直接返回 26 | if (serverChannelId.isEmpty()) { 27 | long start = System.currentTimeMillis(); 28 | while (System.currentTimeMillis() - start < 5000) { 29 | serverChannelId = ClientChannelGroup.getServerChannel(clientChannelId); 30 | if (!serverChannelId.isEmpty()) { 31 | break; 32 | } 33 | Thread.sleep(200); 34 | } 35 | if (serverChannelId.isEmpty()) { 36 | ctx.close(); 37 | return; 38 | } 39 | } 40 | Frame frame = new Frame(); 41 | frame.setReq(clientChannelId); 42 | frame.setRes(serverChannelId); 43 | frame.setCmd(ProcessorEnum.UP_STREAM.getCmd()); 44 | frame.setLen(msg.readableBytes()); 45 | LinkedHashMap dataMap = new LinkedHashMap<>(); 46 | dataMap.put("data", BufUtil.getArray(msg)); 47 | frame.setData(dataMap); 48 | Channel internalChannel = ClientChannelGroup.forkChannel(serverChannelId); 49 | internalChannel.writeAndFlush(frame); 50 | } 51 | 52 | @Override 53 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 54 | ClientChannelGroup.addProxy(ctx.channel()); 55 | ClientChannelGroup.printGroupState(); 56 | } 57 | 58 | @Override 59 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 60 | //清除缓存信息 61 | String clientChannelId = ctx.channel().id().asShortText(); 62 | String serverChannelId = ClientChannelGroup.getServerChannel(clientChannelId); 63 | ClientChannelGroup.removeChannelPair(serverChannelId, clientChannelId); 64 | ClientChannelGroup.removeProxy(ctx.channel()); 65 | } 66 | 67 | /** 68 | * 通道异常触发 69 | * 70 | * @param ctx 71 | * @param cause 72 | * @throws Exception 73 | */ 74 | @Override 75 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 76 | Channel channel = ctx.channel(); 77 | if (!channel.isActive()) { 78 | log.debug("被代理客户端 -- " + channel.remoteAddress() + " 断开了连接!"); 79 | cause.printStackTrace(); 80 | ctx.close(); 81 | } else { 82 | ctx.fireExceptionCaught(cause); 83 | log.error("channel异常:", cause); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/handler/DispatcherHandler.java: -------------------------------------------------------------------------------- 1 | package core.netty.handler; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.entity.Tunnel; 6 | import core.netty.group.ServerChannelGroup; 7 | import core.netty.group.channel.strategy.constant.ForkStrategyEnum; 8 | import core.utils.ByteUtil; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.handler.codec.ByteToMessageDecoder; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | import java.net.InetSocketAddress; 16 | import java.util.LinkedHashMap; 17 | import java.util.List; 18 | 19 | /** 20 | * @author wneck130@gmail.com 21 | * @Description: 协议分发控制器,根据收到的首包消息内容进行判断,区分TCP和HTTP协议 22 | * @date 2021/9/26 23 | */ 24 | @Slf4j 25 | public abstract class DispatcherHandler extends ByteToMessageDecoder { 26 | 27 | @Override 28 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 29 | // Will use the first five bytes to detect a protocol. 30 | if (byteBuf.readableBytes() < 5) { 31 | return; 32 | } 33 | final int magic1 = byteBuf.getUnsignedByte(byteBuf.readerIndex()); 34 | final int magic2 = byteBuf.getUnsignedByte(byteBuf.readerIndex() + 1); 35 | 36 | // 判断是不是HTTP请求 37 | if (isHttp(magic1, magic2)) { 38 | log.info("this is a http msg"); 39 | addHttpHandler(channelHandlerContext); 40 | } else { 41 | log.info("this is a socket msg"); 42 | addTcpHandler(channelHandlerContext); 43 | } 44 | channelHandlerContext.pipeline().remove(this); 45 | } 46 | 47 | @Override 48 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 49 | InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().localAddress(); 50 | log.debug("{}:{}收到新的连接请求", inetSocketAddress.getHostName(), inetSocketAddress.getPort()); 51 | ServerChannelGroup.addProxy(ctx.channel()); 52 | //新的连接建立后进行配对 53 | fullyConnect(ctx); 54 | ServerChannelGroup.printGroupState(); 55 | } 56 | 57 | /** 58 | * 外部请求与ProxyServer激活channel连接时,通知ProxyClient与被代理服务预建立连接 59 | */ 60 | public abstract void fullyConnect(ChannelHandlerContext ctx) throws Exception; 61 | 62 | /** 63 | * 配置http请求的pipeline 64 | * @param ctx 65 | */ 66 | public abstract void addHttpHandler(ChannelHandlerContext ctx); 67 | 68 | /** 69 | * 配置Tcp请求的pipeline 70 | * @param ctx 71 | */ 72 | public abstract void addTcpHandler(ChannelHandlerContext ctx); 73 | 74 | /** 75 | * 判断请求是否是HTTP请求 76 | * 77 | * @param magic1 报文第一个字节 78 | * @param magic2 报文第二个字节 79 | * @return 80 | */ 81 | public boolean isHttp(int magic1, int magic2) { 82 | // GET 83 | return magic1 == 'G' && magic2 == 'E' || 84 | // POST 85 | magic1 == 'P' && magic2 == 'O' || 86 | // PUT 87 | magic1 == 'P' && magic2 == 'U' || 88 | // HEAD 89 | magic1 == 'H' && magic2 == 'E' || 90 | // OPTIONS 91 | magic1 == 'O' && magic2 == 'P' || 92 | // PATCH 93 | magic1 == 'P' && magic2 == 'A' || 94 | // DELETE 95 | magic1 == 'D' && magic2 == 'E' || 96 | // TRACE 97 | magic1 == 'T' && magic2 == 'R' || 98 | // CONNECT 99 | magic1 == 'C' && magic2 == 'O'; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/constant/FrameConstant.java: -------------------------------------------------------------------------------- 1 | package core.constant; 2 | 3 | public class FrameConstant { 4 | /** 5 | * 协议头 6 | */ 7 | public static byte REQ_PV = (byte)0x00; 8 | public static byte RES_PV = (byte)0x01; 9 | 10 | public static byte CMD_LOGIN = (byte)0x01; 11 | 12 | public static byte CMD_PRE_CONNECT = (byte)0x03; 13 | 14 | public static byte CMD_DATA_TRANSFER = (byte)0xFF; 15 | 16 | public static String DEFAULT_CHANNEL_ID = "FFFFFFFF"; 17 | 18 | /** 19 | * 通用成功响应 20 | */ 21 | public static byte RESULT_SUCCESS = (byte)0x00; 22 | 23 | /** 24 | * 通用失败响应 25 | */ 26 | public static byte RESULT_FAIL = (byte)0x01; 27 | 28 | /** 29 | * 数据帧命令字所在位置,起始位置为0 30 | */ 31 | public static int FRAME_CMD_INDEX = 25; 32 | 33 | /** 34 | * 协议序号长度 35 | */ 36 | public static int FRAME_SERIAL_LEN = 8; 37 | 38 | /** 39 | * 数据帧长度参数所在位置 40 | */ 41 | public static int FRAME_LEN_INDEX = 26; 42 | 43 | /** 44 | * 数据帧长度参数的长度 45 | */ 46 | public static int FRAME_LEN_LEN = 4; 47 | 48 | /** 49 | * 数据帧Data部分第一个字节 50 | */ 51 | public static int FRAME_DTAT_FIRST_BYTE_INDEX = 12; 52 | 53 | /** 54 | * 数据帧返回结果所在位置 55 | */ 56 | public static int FRAME_RESULT_INDEX = 12; 57 | 58 | /** 59 | * 数据帧密码长度所在位置 60 | */ 61 | public static int FRAME_PASSWORD_LEN_INDEX = 13; 62 | 63 | /** 64 | * 数据帧返回结果参数长度 65 | */ 66 | public static int FRAME_RESULT_LEN = 1; 67 | 68 | /** 69 | * 校验码长度 70 | */ 71 | public static int VC_CODE_LEN = 1; 72 | 73 | /** 74 | * 数据帧最大字节数 75 | */ 76 | public static int FRAME_MAX_BYTES = 655350000; 77 | 78 | /** 79 | * netty读动作空闲时间,0表示不作控制,单位秒 80 | */ 81 | public static int PIPELINE_READE_TIMEOUT = 0; 82 | 83 | /** 84 | * netty读动作空闲时间,0表示不作控制,单位秒 85 | */ 86 | public static int PIPELINE_READE_TIMEOUT_CONTROLL = 10; 87 | 88 | /** 89 | * netty写动作空闲时间,0表示不作控制,单位秒 90 | */ 91 | public static int PIPELINE_WRITE_TIMEOUT = 0; 92 | 93 | /** 94 | * netty读写动作空闲时间,0表示不作控制,单位秒 95 | */ 96 | public static int PIPELINE_READ_WRITE_TIMEOUT = 10; 97 | 98 | /** 99 | * boss线程组中线程数 100 | */ 101 | public static int BOSSGROUP_NUM = 1; 102 | 103 | /** 104 | * TCP优化so_backlog参数 105 | */ 106 | public static int TCP_SO_BACKLOG = 1024; 107 | 108 | /** 109 | * 内部通信channel阻止系统使用negale算法进行TCP并包发送 110 | */ 111 | public static boolean TCP_NODELAY = true; 112 | 113 | /** 114 | * TCP重连尝试次数 115 | */ 116 | public static int TCP_CONNECTION_RETRY_NUM = 10; 117 | 118 | /** 119 | * 当端口处于TIME_WAIT时是否允许其他程序监听该端口,用于服务重启 120 | */ 121 | public static boolean TCP_REUSE_ADDR = true; 122 | 123 | /** 124 | * TCP重连尝试间隔扩大倍数 125 | */ 126 | public static int TCP_RETRY_INTERVAL_ADD = 2; 127 | 128 | /** 129 | * 连接池大小的长度 130 | */ 131 | public static int CHANNEL_POOL_INIT_NUM = 100; 132 | 133 | /** 134 | * 连接池大小的长度 135 | */ 136 | public static int CHANNEL_POOL_NUM_LEN = 1; 137 | 138 | /** 139 | * 心跳超时时间 140 | */ 141 | public static int HEARTBEAT_TIMEOUT = 15; 142 | 143 | /** 144 | * channel回收延迟,单位秒 145 | */ 146 | public static int CHANNEL_RELEASE_DELAY = 2; 147 | 148 | public static long HEARTBEAT_INTERVAL = 10L; 149 | 150 | public static String DEFAULT_CHARSET = "UTF-8"; 151 | 152 | /** 153 | * 数据帧最小长度 154 | */ 155 | public static int FRAME_MIN_LEN = 14; 156 | 157 | 158 | public static int CHANNELID_LEN = 64; 159 | } 160 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.scrat 8 | netty-nat 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | 13 | nat-client 14 | nat-server 15 | nat-core 16 | 17 | 18 | 4.1.49.Final 19 | 2.11.0 20 | 1.18.20 21 | 1.7.25 22 | 2.17.2 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.slf4j 30 | slf4j-api 31 | ${sjf4j-api-version} 32 | 33 | 34 | 35 | org.apache.logging.log4j 36 | log4j-slf4j-impl 37 | ${log4j2.version} 38 | 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-api 43 | ${log4j2.version} 44 | 45 | 46 | org.apache.logging.log4j 47 | log4j-core 48 | ${log4j2.version} 49 | 50 | 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | ${lombok-version} 56 | 57 | 58 | io.netty 59 | netty-all 60 | ${netty-version} 61 | 62 | 63 | com.fasterxml.jackson.core 64 | jackson-core 65 | ${jackson-version} 66 | 67 | 68 | com.fasterxml.jackson.core 69 | jackson-databind 70 | ${jackson-version} 71 | 72 | 73 | com.fasterxml.jackson.core 74 | jackson-annotations 75 | ${jackson-version} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-compiler-plugin 85 | 3.8.1 86 | 87 | 1.8 88 | 1.8 89 | UTF-8 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/properties/loader/PropertiesLoader.java: -------------------------------------------------------------------------------- 1 | package core.properties.loader; 2 | 3 | import core.properties.cache.PropertiesCache; 4 | import core.properties.filewatch.FileWatchService; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.Properties; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | 16 | /** 17 | * ClassName: PropertiesLoader 18 | * Function: 加载.properties的配置文件加载器,. 19 | * date: 2017年3月20日 上午10:47:55 20 | * 21 | * @author wneck130@gmail.com 22 | * @version 23 | * @since JDK 1.8 24 | */ 25 | public class PropertiesLoader extends AbstractLoader { 26 | 27 | private final Logger logger = LogManager.getLogger(PropertiesLoader.class); 28 | 29 | public PropertiesLoader(String... targetFilenames) { 30 | super(targetFilenames); 31 | } 32 | 33 | @Override 34 | public void load(String path) throws Exception { 35 | Properties properties = new Properties(); 36 | //读取文件 37 | File propFile = readFiles(path); 38 | if (propFile == null) { 39 | throw new Exception("无法加载配置文件!!!"); 40 | } 41 | FileInputStream in = new FileInputStream(propFile); 42 | properties.load(in); 43 | for (Object key : properties.keySet()) { 44 | PropertiesCache.getInstance().getProps().putIfAbsent(key.toString(), properties.get(key).toString()); 45 | } 46 | //加载完成后,启动一个FileWatchService来对文件修改状态进行监控,如果监听到文件修改,则重新加载修改后的文件内容 47 | addWatch(propFile.getParent()); 48 | } 49 | 50 | @Override 51 | public void reload(String path) throws Exception { 52 | Properties properties = new Properties(); 53 | //读取文件 54 | File propFile = readFiles(path); 55 | if (!propFile.exists()) { 56 | return; 57 | } 58 | FileInputStream in = new FileInputStream(propFile); 59 | properties.load(in); 60 | for (Object key : properties.keySet()) { 61 | PropertiesCache.getInstance().getProps().putIfAbsent(key.toString(), properties.get(key).toString()); 62 | } 63 | } 64 | 65 | /** 66 | * 添加监控 67 | * @param path 68 | */ 69 | public void addWatch(String path) { 70 | FileWatchService service = new FileWatchService(); 71 | new Thread(() -> service.addWatcher(path, this)).start(); 72 | } 73 | 74 | /** 75 | * 读取指定路径下的文件,按文件层级由浅到深返回第一个匹配的properties文件 76 | * @param path 77 | * @return 78 | */ 79 | public File readFiles(String path) { 80 | try { 81 | File dir = new File(path); 82 | List subFiles = Stream.of(dir.listFiles()).filter((file) -> file.isFile()).collect(Collectors.toList()); 83 | if (!subFiles.isEmpty()) { 84 | Optional optional = subFiles.stream().filter((file) -> file.getName().equalsIgnoreCase("properties.properties")).findFirst(); 85 | //当前目录下有需要的文件,直接返回 86 | if (optional.isPresent()) { 87 | logger.debug("找到配置文件:" + ((File) optional.get()).getAbsolutePath()); 88 | return (File) optional.get(); 89 | } 90 | } 91 | //当前目录下没有,则查找子目录 92 | List subDirs = Stream.of(dir.listFiles()).filter((file) -> file.isDirectory()).collect(Collectors.toList()); 93 | if (!subDirs.isEmpty()) { 94 | for (File subDir : subDirs) { 95 | File subFile = readFiles(subDir.getPath()); 96 | if (subFile != null) { 97 | logger.debug("找到配置文件:" + subFile.getAbsolutePath()); 98 | return subFile; 99 | } 100 | } 101 | } 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } 105 | return null; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /nat-client/src/main/java/client/internal/handler/processor/PreConnectProcessor.java: -------------------------------------------------------------------------------- 1 | package client.internal.handler.processor; 2 | 3 | import core.netty.group.ClientChannelGroup; 4 | import client.internal.handler.processor.constant.ProcessorEnum; 5 | import client.proxy.ProxyNettyClient; 6 | import core.constant.FrameConstant; 7 | import core.entity.Frame; 8 | import core.netty.handler.processor.Processor; 9 | import core.utils.BufUtil; 10 | import core.utils.ByteUtil; 11 | import io.netty.buffer.ByteBuf; 12 | import io.netty.channel.ChannelFuture; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | import java.util.LinkedHashMap; 17 | import java.util.Objects; 18 | import java.util.UnknownFormatConversionException; 19 | 20 | /** 21 | * @Author wneck130@gmail.com 22 | * @function 预创建连接命令处理器(cmd:0x03),命令由proxyServer发起请求,proxyClient回复响应 23 | */ 24 | @Slf4j 25 | public class PreConnectProcessor implements Processor { 26 | 27 | @Override 28 | public Frame assemble(ByteBuf in) throws Exception { 29 | try { 30 | //解析协议公共部分 31 | Frame frame = new Frame().quickHead(in); 32 | int hostSegment1 = in.readByte() & 0xFF; 33 | int hostSegment2 = in.readByte() & 0xFF; 34 | int hostSegment3 = in.readByte() & 0xFF; 35 | int hostSegment4 = in.readByte() & 0xFF; 36 | String host = new StringBuilder().append(hostSegment1) 37 | .append(".") 38 | .append(hostSegment2) 39 | .append(".") 40 | .append(hostSegment3) 41 | .append(".") 42 | .append(hostSegment4).toString(); 43 | int port = in.readUnsignedShort(); 44 | LinkedHashMap dataMap = new LinkedHashMap<>(); 45 | dataMap.put("host", host); 46 | dataMap.put("port", port); 47 | frame.setData(dataMap); 48 | return frame; 49 | } catch (Exception e) { 50 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 51 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 52 | } 53 | } 54 | 55 | @Override 56 | public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 57 | try { 58 | //收到服务器的命令后主动建立与被代理服务之间的连接 59 | String host = (String) msg.getData().get("host"); 60 | int port = (Integer) msg.getData().get("port"); 61 | ProxyNettyClient proxyNettyClient = new ProxyNettyClient(); 62 | proxyNettyClient.setHost(host); 63 | proxyNettyClient.setPort(port); 64 | proxyNettyClient.start(); 65 | ChannelFuture future = proxyNettyClient.getFuture(); 66 | //创建失败 67 | if (future == null) { 68 | response(ctx, msg, FrameConstant.RESULT_FAIL, FrameConstant.DEFAULT_CHANNEL_ID); 69 | return; 70 | } 71 | //创建成功后缓存配对关系,发送响应消息 72 | ClientChannelGroup.addChannelPair(msg.getReq(), future.channel().id().asShortText()); 73 | log.debug("创建channelPair:[serverChannel:{}], [clientChannel:{}]", msg.getReq(), 74 | future.channel().id().asShortText()); 75 | response(ctx, msg, FrameConstant.RESULT_SUCCESS, future.channel().id().asShortText()); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | log.error("预创建连接请求异常!!!"); 79 | } 80 | } 81 | 82 | /** 83 | * 拼接响应内容 84 | * @param ctx 85 | * @param msg 86 | * @param result 87 | */ 88 | public void response(ChannelHandlerContext ctx, Frame msg, byte result, String proxyChannelId) { 89 | Frame response = new Frame(); 90 | response.setPv(FrameConstant.RES_PV); 91 | response.setSerial(msg.getSerial()); 92 | response.setReq(msg.getReq()); 93 | response.setRes(proxyChannelId); 94 | response.setLen(1); 95 | response.setCmd(msg.getCmd()); 96 | LinkedHashMap dataMap = new LinkedHashMap<>(); 97 | dataMap.put("result", result); 98 | response.setData(dataMap); 99 | ctx.writeAndFlush(response); 100 | } 101 | 102 | @Override 103 | public boolean supply(byte cmd) { 104 | return Objects.equals(ProcessorEnum.PRE_CONNECT, cmd); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/properties/loader/YamlLoader.java: -------------------------------------------------------------------------------- 1 | package core.properties.loader; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 5 | import core.properties.cache.PropertiesCache; 6 | import core.properties.filewatch.FileWatchService; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.File; 10 | import java.util.*; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | /** 15 | * @author wneck130@gmail.com 16 | * @Description: 17 | * @date 2021/9/30 18 | */ 19 | @Slf4j 20 | public class YamlLoader extends AbstractLoader { 21 | 22 | private static final String EXTENDTION = "."; 23 | 24 | public YamlLoader(String... targetFilenames) { 25 | super(targetFilenames); 26 | } 27 | 28 | @Override 29 | public void load(String path) throws Exception { 30 | //读取文件 31 | File propFile = readFiles(path); 32 | if (propFile == null) { 33 | throw new Exception("无法加载配置文件!!!"); 34 | } 35 | ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); 36 | Map origin = mapper.readValue(propFile, Map.class); 37 | unfold(origin).forEach((key, value) -> PropertiesCache.getInstance().getProps().putIfAbsent(key, value)); 38 | //加载完成后,启动一个FileWatchService来对文件修改状态进行监控,如果监听到文件修改,则重新加载修改后的文件内容 39 | addWatch(propFile.getParent()); 40 | } 41 | 42 | @Override 43 | public void reload(String path) throws Exception { 44 | //读取文件 45 | File propFile = readFiles(path); 46 | if (propFile == null) { 47 | throw new Exception("无法加载配置文件!!!"); 48 | } 49 | ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); 50 | Map origin = mapper.readValue(propFile, Map.class); 51 | unfold(origin).forEach((key, value) -> PropertiesCache.getInstance().getProps().putIfAbsent(key, value)); 52 | } 53 | 54 | /** 55 | * 添加监控 56 | * @param path 57 | */ 58 | public void addWatch(String path) { 59 | FileWatchService service = new FileWatchService(); 60 | new Thread(() -> service.addWatcher(path, this)).start(); 61 | } 62 | 63 | public Map unfold(Map origin) { 64 | Map target = new HashMap<>(); 65 | origin.forEach((key, value) -> { 66 | String prefix = key.toString(); 67 | // 68 | if (value instanceof LinkedHashMap) { 69 | Map subMapOrigin = unfold((Map)value); 70 | target.putAll(subMapOrigin.entrySet().stream().collect(Collectors.toMap( 71 | stringObjectEntry -> prefix+ EXTENDTION+stringObjectEntry.getKey(), 72 | stringObjectEntry -> stringObjectEntry.getValue()))); 73 | } else { 74 | target.put(key, value); 75 | } 76 | }); 77 | return target; 78 | } 79 | 80 | /** 81 | * 读取指定路径下的文件,按文件层级由浅到深返回第一个匹配的properties.yml文件 82 | * @param path 83 | * @return 84 | */ 85 | public File readFiles(String path) { 86 | try{ 87 | File dir = new File(path); 88 | List subFiles = Stream.of(dir.listFiles()).filter(File::isFile).collect(Collectors.toList()); 89 | if (!subFiles.isEmpty()) { 90 | Optional optional = subFiles.stream().filter((file) -> file.getName().equalsIgnoreCase("properties.yml")).findFirst(); 91 | //当前目录下有需要的文件,直接返回 92 | if (optional.isPresent()) { 93 | log.debug("找到配置文件:" + optional.get().getAbsolutePath()); 94 | return optional.get(); 95 | } 96 | } 97 | //当前目录下没有,则查找子目录 98 | List subDirs = Stream.of(dir.listFiles()).filter(File::isDirectory).collect(Collectors.toList()); 99 | if (!subDirs.isEmpty()) { 100 | for (File subDir : subDirs) { 101 | File subFile = readFiles(subDir.getPath()); 102 | if (subFile != null) { 103 | log.debug("找到配置文件:" + subFile.getAbsolutePath()); 104 | return subFile; 105 | } 106 | } 107 | } 108 | }catch(Exception e){ 109 | e.printStackTrace(); 110 | log.error("在路径{}下查找配置文件发生异常:", path, e); 111 | } 112 | return null; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/channel/message/MessageContext.java: -------------------------------------------------------------------------------- 1 | package core.netty.group.channel.message; 2 | 3 | import core.entity.Frame; 4 | import core.netty.group.channel.message.receiver.listener.ResponseListener; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.ScheduledThreadPoolExecutor; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @author wneck130@gmail.com 16 | * @Description: 17 | * @date 2021/9/28 18 | */ 19 | public class MessageContext { 20 | 21 | public static ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(1); 22 | 23 | private MessageContext(){ 24 | //对缓存的所有消息进行超时判断,间隔1秒 25 | scheduled.scheduleAtFixedRate(() -> 26 | historyMessage.entrySet().stream().forEach(stringListEntry -> { 27 | String channelId = stringListEntry.getKey(); 28 | stringListEntry.getValue().stream() 29 | .filter(request -> request.getSerial() < System.currentTimeMillis() - 30000L) 30 | .forEach(timeoutRequest -> { 31 | ResponseEvent responseEvent = new ResponseEvent(this); 32 | responseEvent.setRequest(timeoutRequest); 33 | responseEvent.setChannelId(channelId); 34 | this.notifyTimeout(responseEvent); 35 | }); 36 | }), 1, 1, TimeUnit.SECONDS); 37 | } 38 | 39 | public static MessageContext getInstance() { 40 | return Holder.instance; 41 | } 42 | 43 | private static class Holder { 44 | private static MessageContext instance = new MessageContext(); 45 | 46 | } 47 | 48 | private List listeners = new ArrayList<>(); 49 | 50 | /** 51 | * 发送历史消息合集,当发送消息后添加了响应监听器,则缓存当前发送的消息 52 | * Key为proxyServer的channelId,value为消息frame的serial字段值 53 | */ 54 | private static Map> historyMessage = new ConcurrentHashMap<>(); 55 | 56 | public static void addHistoryFrame(String channelId, Frame frame) { 57 | if (historyMessage.containsKey(channelId)) { 58 | List frames = historyMessage.get(channelId); 59 | if (frames == null) { 60 | frames = new ArrayList<>(); 61 | } 62 | frames.add(frame); 63 | historyMessage.put(channelId, frames); 64 | } else { 65 | List frames = new ArrayList<>(); 66 | frames.add(frame); 67 | historyMessage.put(channelId, frames); 68 | } 69 | } 70 | 71 | /** 72 | * 清除消息发送记录 73 | * @param channelId 74 | * @param frame 75 | * @return 76 | */ 77 | private void removeHistory(String channelId, Frame frame) { 78 | if (!historyMessage.containsKey(channelId)) { 79 | return; 80 | } 81 | List frames = historyMessage.get(channelId); 82 | if (frames == null || frames.isEmpty()) { 83 | return; 84 | } 85 | frames.remove(frame); 86 | historyMessage.put(channelId, frames); 87 | } 88 | 89 | public static Frame getHistoryFrame(String channelId, Long serial) { 90 | List historyRequest = historyMessage.get(channelId); 91 | if (historyRequest == null) { 92 | return null; 93 | } 94 | return historyRequest.stream().filter(frame -> Objects.equals(frame.getSerial(), serial)).findFirst().get(); 95 | } 96 | 97 | public void addResponseListener(ResponseListener responseListener) { 98 | if (!listeners.contains(responseListener)) { 99 | listeners.add(responseListener); 100 | } 101 | } 102 | 103 | /** 104 | * 响应超时消息 105 | */ 106 | public void notifyTimeout(ResponseEvent responseEvent) { 107 | listeners.forEach(responseListener -> responseListener.timeout(responseEvent)); 108 | removeHistory(responseEvent.getChannelId(), responseEvent.getRequest()); 109 | } 110 | 111 | /** 112 | * 响应事件通知 113 | * @param responseEvent 114 | */ 115 | public void notifyResponse(ResponseEvent responseEvent) { 116 | byte cmd = responseEvent.getRequest().getCmd(); 117 | listeners.stream().filter(responseListener -> responseListener.supply(cmd)) 118 | .forEach(responseListener -> responseListener.response(responseEvent)); 119 | removeHistory(responseEvent.getChannelId(), responseEvent.getRequest()); 120 | } 121 | 122 | 123 | 124 | } 125 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/proxy/handler/TcpProxyServerHandler.java: -------------------------------------------------------------------------------- 1 | package server.proxy.handler; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.entity.Tunnel; 6 | import core.netty.group.ServerChannelGroup; 7 | import core.netty.group.channel.strategy.constant.ForkStrategyEnum; 8 | import core.utils.ByteUtil; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.channel.SimpleChannelInboundHandler; 13 | import lombok.extern.slf4j.Slf4j; 14 | import server.internal.handler.processor.constant.ProcessorEnum; 15 | 16 | import java.net.InetSocketAddress; 17 | import java.util.LinkedHashMap; 18 | 19 | /** 20 | * @Author wneck130@gmail.com 21 | * @Function proxyServer业务handler 22 | */ 23 | @Slf4j 24 | public class TcpProxyServerHandler extends SimpleChannelInboundHandler { 25 | 26 | /** 27 | * 数据传输时触发 28 | * 29 | * @param ctx 30 | * @param msg 31 | * @throws Exception 32 | */ 33 | @Override 34 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 35 | //收到外部的msg消息,首先挑选一条internalChannel准备进行数据转发 36 | String serverChannelId = ctx.channel().id().asShortText(); 37 | String clientChannelId = ServerChannelGroup.getClientByServer(serverChannelId); 38 | //没有对应的服务端channel,直接返回 39 | if (clientChannelId.isEmpty()) { 40 | long start = System.currentTimeMillis(); 41 | while (System.currentTimeMillis() - start < 5000) { 42 | clientChannelId = ServerChannelGroup.getClientByServer(serverChannelId); 43 | if (!clientChannelId.isEmpty()) { 44 | break; 45 | } 46 | Thread.sleep(200); 47 | } 48 | if (clientChannelId.isEmpty()) { 49 | ctx.close(); 50 | return; 51 | } 52 | } 53 | Channel internalChannel = ServerChannelGroup.forkChannel(ForkStrategyEnum.MIN_LOAD); 54 | //第二步,封装成内部通信的指定格式 55 | byte[] message = new byte[msg.readableBytes()]; 56 | msg.readBytes(message); 57 | LinkedHashMap map = new LinkedHashMap<>(1); 58 | map.put("data", message); 59 | Frame frame = new Frame(); 60 | frame.setCmd(ProcessorEnum.DOWN_STREAM.getCmd()); 61 | frame.setReq(serverChannelId); 62 | frame.setRes(clientChannelId); 63 | frame.setData(map); 64 | internalChannel.writeAndFlush(frame); 65 | } 66 | 67 | /** 68 | * 通道连接成功触发 69 | * 70 | * @param ctx 71 | * @throws Exception 72 | */ 73 | @Override 74 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 75 | InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().localAddress(); 76 | log.debug("{}:{}收到新的连接请求", inetSocketAddress.getHostName(), inetSocketAddress.getPort()); 77 | ServerChannelGroup.addProxy(ctx.channel()); 78 | //新的连接建立后进行配对 79 | fullyConnect(ctx); 80 | ServerChannelGroup.printGroupState(); 81 | } 82 | 83 | /** 84 | * 外部请求与ProxyServer激活channel连接时,通知ProxyClient与被代理服务预建立连接 85 | */ 86 | public void fullyConnect(ChannelHandlerContext ctx) throws Exception { 87 | //发送启动代理客户端命令 88 | Frame frame = new Frame(); 89 | frame.setReq(ctx.channel().id().asShortText()); 90 | frame.setRes(FrameConstant.DEFAULT_CHANNEL_ID); 91 | frame.setCmd(ProcessorEnum.PRE_CONNECT.getCmd()); 92 | //通过当前channel的parent channel获取对应的tunnelId 93 | Tunnel tunnel = ServerChannelGroup.getTunnelByChannel(ctx.channel().parent()); 94 | Channel internalChannel = ServerChannelGroup.forkChannel(ForkStrategyEnum.MIN_LOAD); 95 | LinkedHashMap data = new LinkedHashMap<>(1); 96 | String[] clientHostSegment = tunnel.getClientHost().split("\\."); 97 | data.put("host", new byte[]{ByteUtil.fromInt(Integer.parseInt(clientHostSegment[0]))[3], 98 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[1]))[3], 99 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[2]))[3], 100 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[3]))[3]}); 101 | data.put("port", new byte[]{ByteUtil.fromInt(tunnel.getClientPort())[2], 102 | ByteUtil.fromInt(tunnel.getClientPort())[3]}); 103 | frame.setData(data); 104 | internalChannel.writeAndFlush(frame); 105 | } 106 | 107 | @Override 108 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 109 | ServerChannelGroup.removeProxy(ctx.channel()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/utils/StringUtil.java: -------------------------------------------------------------------------------- 1 | package core.utils; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Random; 8 | 9 | /** 10 | * 11 | * @author wneck130@gmail.com 12 | */ 13 | public final class StringUtil { 14 | 15 | /** 16 | * Check if a string is empty 17 | * @param s 18 | * @return 19 | */ 20 | public static boolean isEmpty(String s){ 21 | return s == null || s.trim().length() == 0; 22 | } 23 | 24 | /** 25 | * Encrypt string s with MD5. 26 | * @param s 27 | * @return 28 | */ 29 | public static String encodeMd5(String s){ 30 | if(isEmpty(s)){ 31 | return null; 32 | } 33 | MessageDigest md = null; 34 | try{ 35 | md = MessageDigest.getInstance("MD5"); 36 | }catch (NoSuchAlgorithmException ex) { 37 | //ignore ex 38 | return null; 39 | } 40 | char[] hexDigits = { '0', '1', '2', '3', '4', 41 | '5', '6', '7', '8', '9', 42 | 'A', 'B', 'C', 'D', 'E', 'F' }; 43 | md.update(s.getBytes()); 44 | byte[] datas = md.digest(); 45 | int len = datas.length; 46 | char str[] = new char[len * 2]; 47 | int k = 0; 48 | for (int i = 0; i < len; i++) { 49 | byte byte0 = datas[i]; 50 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 51 | str[k++] = hexDigits[byte0 & 0xf]; 52 | } 53 | return new String(str); 54 | } 55 | 56 | public static String getArgument(String name, String[] args){ 57 | if(args==null){ 58 | return null; 59 | } 60 | for(int i=0; i 2 | 5 | 6 | netty-nat 7 | com.scrat 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | nat-client 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-api 18 | 19 | 20 | 21 | org.apache.logging.log4j 22 | log4j-slf4j-impl 23 | 24 | 25 | 26 | org.apache.logging.log4j 27 | log4j-api 28 | 29 | 30 | org.apache.logging.log4j 31 | log4j-core 32 | 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | 38 | 39 | io.netty 40 | netty-all 41 | 42 | 43 | com.fasterxml.jackson.core 44 | jackson-core 45 | 46 | 47 | com.fasterxml.jackson.core 48 | jackson-databind 49 | 50 | 51 | com.fasterxml.jackson.core 52 | jackson-annotations 53 | 54 | 55 | com.scrat 56 | nat-core 57 | 1.0-SNAPSHOT 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-jar-plugin 65 | 3.1.1 66 | 67 | 68 | 69 | true 70 | libs/ 71 | client.internal.InternalNettyClient 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-dependency-plugin 80 | 3.1.1 81 | 82 | 83 | copy-lib 84 | prepare-package 85 | 86 | copy-dependencies 87 | 88 | 89 | ${project.build.directory}/libs 90 | false 91 | false 92 | true 93 | compile 94 | 95 | 96 | 97 | 98 | 99 | 100 | maven-assembly-plugin 101 | 102 | false 103 | 104 | assembly.xml 105 | 106 | 107 | 108 | 109 | make-assembly 110 | package 111 | 112 | single 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /nat-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | netty-nat 7 | com.scrat 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | nat-server 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-api 18 | 19 | 20 | 21 | org.apache.logging.log4j 22 | log4j-slf4j-impl 23 | 24 | 25 | 26 | org.apache.logging.log4j 27 | log4j-api 28 | 29 | 30 | org.apache.logging.log4j 31 | log4j-core 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | 37 | 38 | io.netty 39 | netty-all 40 | 41 | 42 | com.fasterxml.jackson.core 43 | jackson-core 44 | 45 | 46 | com.fasterxml.jackson.core 47 | jackson-databind 48 | 49 | 50 | com.fasterxml.jackson.core 51 | jackson-annotations 52 | 53 | 54 | com.scrat 55 | nat-core 56 | 1.0-SNAPSHOT 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-jar-plugin 65 | 3.1.1 66 | 67 | 68 | 69 | true 70 | libs/ 71 | server.internal.InternalNettyServer 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-dependency-plugin 80 | 3.1.1 81 | 82 | 83 | copy-lib 84 | prepare-package 85 | 86 | copy-dependencies 87 | 88 | 89 | ${project.build.directory}/libs 90 | false 91 | false 92 | true 93 | compile 94 | 95 | 96 | 97 | 98 | 99 | 100 | maven-assembly-plugin 101 | 102 | false 103 | 104 | assembly.xml 105 | 106 | 107 | 108 | 109 | make-assembly 110 | package 111 | 112 | single 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/crypto/Crypto.java: -------------------------------------------------------------------------------- 1 | package core.crypto; 2 | 3 | import java.security.AlgorithmParameters; 4 | import java.security.InvalidAlgorithmParameterException; 5 | import java.security.InvalidKeyException; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.security.spec.InvalidParameterSpecException; 8 | import javax.crypto.BadPaddingException; 9 | import javax.crypto.Cipher; 10 | import javax.crypto.IllegalBlockSizeException; 11 | import javax.crypto.NoSuchPaddingException; 12 | import javax.crypto.spec.IvParameterSpec; 13 | import javax.crypto.spec.SecretKeySpec; 14 | import org.apache.logging.log4j.*; 15 | 16 | /** 17 | * AES128 encryptor and decryptor. Code copied from bugu-droid. 18 | * 19 | * @author wneck130@gmail.com 20 | */ 21 | public class Crypto { 22 | 23 | private final static Logger logger = LogManager.getLogger(Crypto.class); 24 | 25 | private String mAlgorithm; 26 | private Cipher mCipher; 27 | private AlgorithmParameters mAlgorithmParameters; 28 | private PaddingBytes mPadding; 29 | 30 | public Crypto(String algorithm, String blockmode) { 31 | mAlgorithm = algorithm; 32 | 33 | try { 34 | mAlgorithmParameters = AlgorithmParameters.getInstance(algorithm); 35 | } catch (NoSuchAlgorithmException e) { 36 | System.out.println(e.getMessage()); 37 | } 38 | 39 | try { 40 | String transformation = algorithm; 41 | if (blockmode != null) { 42 | transformation += "/" + blockmode; 43 | } 44 | mCipher = Cipher.getInstance(transformation); 45 | } catch (NoSuchAlgorithmException ex) { 46 | logger.error(ex.getMessage(), ex); 47 | } catch (NoSuchPaddingException ex) { 48 | logger.error(ex.getMessage(), ex); 49 | } 50 | } 51 | 52 | public void setIv(byte[] iv) { 53 | IvParameterSpec parameterData = new IvParameterSpec(iv); 54 | try { 55 | mAlgorithmParameters.init(parameterData); 56 | } catch (InvalidParameterSpecException ex) { 57 | logger.error(ex.getMessage(), ex); 58 | } 59 | } 60 | 61 | public void setPaddingBytes(PaddingBytes padding) { 62 | mPadding = padding; 63 | } 64 | 65 | public byte[] encrypt(byte[] plain, byte[] key, byte[] iv) { 66 | IvParameterSpec parameterData = new IvParameterSpec(iv); 67 | SecretKeySpec skeySpec = new SecretKeySpec(key, mAlgorithm); 68 | try { 69 | mCipher.init(Cipher.ENCRYPT_MODE, skeySpec, parameterData); 70 | } catch (InvalidKeyException ex) { 71 | logger.error(ex.getMessage(), ex); 72 | } catch (InvalidAlgorithmParameterException ex) { 73 | logger.error(ex.getMessage(), ex); 74 | } 75 | return encrypt(plain); 76 | } 77 | 78 | public byte[] encrypt(byte[] plain, byte[] key) { 79 | SecretKeySpec skeySpec = new SecretKeySpec(key, mAlgorithm); 80 | try { 81 | mCipher.init(Cipher.ENCRYPT_MODE, skeySpec, mAlgorithmParameters); 82 | } catch (InvalidKeyException ex) { 83 | logger.error(ex.getMessage(), ex); 84 | } catch (InvalidAlgorithmParameterException ex) { 85 | logger.error(ex.getMessage(), ex); 86 | } 87 | return encrypt(plain); 88 | } 89 | 90 | private byte[] encrypt(byte[] plain) { 91 | if (mPadding != null) { 92 | plain = mPadding.addPaddingBytes(plain); 93 | } 94 | byte[] encrypted = null; 95 | try { 96 | encrypted = mCipher.doFinal(plain); 97 | } catch (IllegalBlockSizeException ex) { 98 | logger.error(ex.getMessage(), ex); 99 | } catch (BadPaddingException ex) { 100 | logger.error(ex.getMessage(), ex); 101 | } 102 | return encrypted; 103 | } 104 | 105 | public byte[] decrypt(byte[] coded, byte[] key, byte[] iv) { 106 | SecretKeySpec skeySpec = new SecretKeySpec(key, mAlgorithm); 107 | IvParameterSpec ivspec = new IvParameterSpec(iv); 108 | try { 109 | mCipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec); 110 | } catch (InvalidKeyException ex) { 111 | logger.error(ex.getMessage(), ex); 112 | } catch (InvalidAlgorithmParameterException ex) { 113 | logger.error(ex.getMessage(), ex); 114 | } 115 | 116 | return decrypt(coded); 117 | } 118 | 119 | public byte[] decrypt(byte[] coded, byte[] key) { 120 | SecretKeySpec skeySpec = new SecretKeySpec(key, mAlgorithm); 121 | try { 122 | mCipher.init(Cipher.DECRYPT_MODE, skeySpec, mAlgorithmParameters); 123 | } catch (InvalidKeyException ex) { 124 | logger.error(ex.getMessage(), ex); 125 | } catch (InvalidAlgorithmParameterException ex) { 126 | logger.error(ex.getMessage(), ex); 127 | } 128 | return decrypt(coded); 129 | } 130 | 131 | private byte[] decrypt(byte[] coded) { 132 | byte[] decrypted = null; 133 | try { 134 | decrypted = mCipher.doFinal(coded); 135 | } catch (IllegalBlockSizeException ex) { 136 | logger.error(ex.getMessage(), ex); 137 | } catch (BadPaddingException ex) { 138 | logger.error(ex.getMessage(), ex); 139 | } 140 | 141 | if (mPadding != null) { 142 | decrypted = mPadding.removePaddingBytes(decrypted); 143 | } 144 | return decrypted; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/handler/processor/LoginProcessor.java: -------------------------------------------------------------------------------- 1 | package server.internal.handler.processor; 2 | 3 | import core.constant.FrameConstant; 4 | import core.entity.Frame; 5 | import core.entity.Tunnel; 6 | import core.netty.group.ServerChannelGroup; 7 | import core.netty.handler.processor.Processor; 8 | import core.properties.cache.PropertiesCache; 9 | import core.utils.BufUtil; 10 | import core.utils.ByteUtil; 11 | import io.netty.buffer.ByteBuf; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import lombok.extern.slf4j.Slf4j; 14 | import server.internal.handler.processor.constant.ProcessorEnum; 15 | import server.proxy.ProxyNettyServer; 16 | 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.*; 19 | 20 | /** 21 | * @Author wneck130@gmail.com 22 | * @Function 接入命令处理器(cmd:0x01),命令由internalClient发起请求,internalServer回复响应 23 | */ 24 | @Slf4j 25 | public class LoginProcessor implements Processor { 26 | 27 | private final String DATA_KEY_PASSWORD = "password"; 28 | 29 | private final String DATA_KEY_TUNNELS = "tunnels"; 30 | 31 | private final String DEFAULT_PASSWORD = PropertiesCache.getInstance().get("password"); 32 | 33 | /** 34 | * login数据帧处理 35 | * @param in netty获取的TCP数据流 36 | * @return 37 | * @throws Exception 38 | */ 39 | @Override 40 | public Frame assemble(ByteBuf in) throws Exception { 41 | try { 42 | //解析协议公共部分 43 | Frame frame = new Frame().quickHead(in); 44 | //解析协议data部分 45 | byte passwordLen = in.readByte(); 46 | byte[] passwordBytes = new byte[passwordLen & 0xFF]; 47 | in.readBytes(passwordBytes); 48 | String password = new String(passwordBytes, StandardCharsets.UTF_8); 49 | List tunnelList = new ArrayList<>(); 50 | while (in.readableBytes() > 0) { 51 | int serverPort = in.readUnsignedShort(); 52 | int clientHostSegment1 = in.readByte() & 0xFF; 53 | int clientHostSegment2 = in.readByte() & 0xFF; 54 | int clientHostSegment3 = in.readByte() & 0xFF; 55 | int clientHostSegment4 = in.readByte() & 0xFF; 56 | String clientHost = new StringBuilder().append(clientHostSegment1) 57 | .append(".") 58 | .append(clientHostSegment2) 59 | .append(".") 60 | .append(clientHostSegment3) 61 | .append(".") 62 | .append(clientHostSegment4).toString(); 63 | int clientPort = in.readUnsignedShort(); 64 | Tunnel tunnel = new Tunnel(); 65 | tunnel.setServerPort(serverPort); 66 | tunnel.setClientHost(clientHost); 67 | tunnel.setClientPort(clientPort); 68 | tunnelList.add(tunnel); 69 | } 70 | LinkedHashMap dataMap = new LinkedHashMap<>(2); 71 | dataMap.put(DATA_KEY_PASSWORD, password); 72 | dataMap.put(DATA_KEY_TUNNELS, tunnelList); 73 | frame.setData(dataMap); 74 | return frame; 75 | } catch (Exception e) { 76 | log.error("无法解析的消息: " + ByteUtil.toHexString(BufUtil.getArray(in))); 77 | throw new UnknownFormatConversionException("无法解析的消息!!!"); 78 | } 79 | } 80 | 81 | /** 82 | * login消息处理业务 83 | * @param ctx netty channel上下文 84 | * @param msg 解析成Frame结构的TCP请求数据帧 85 | * @throws Exception 86 | */ 87 | @Override 88 | public void request(ChannelHandlerContext ctx, Frame msg) throws Exception { 89 | Map requestData = msg.getData(); 90 | //响应消息 91 | Frame response = new Frame(); 92 | response.setPv(FrameConstant.RES_PV); 93 | response.setSerial(msg.getSerial()); 94 | response.setRes(msg.getRes()); 95 | response.setReq(msg.getReq()); 96 | response.setCmd(ProcessorEnum.LOGIN.getCmd()); 97 | LinkedHashMap responseData = new LinkedHashMap<>(1); 98 | responseData.put("result", FrameConstant.RESULT_SUCCESS); 99 | response.setData(responseData); 100 | String password = (String) requestData.get(DATA_KEY_PASSWORD); 101 | //密码校验失败,直接返回错误信息并关闭channel 102 | if (!Objects.equals(DEFAULT_PASSWORD, password)) { 103 | responseData.put("result", FrameConstant.RESULT_FAIL); 104 | ctx.writeAndFlush(response); 105 | ctx.close(); 106 | return; 107 | } 108 | List tunnels = (List) requestData.get(DATA_KEY_TUNNELS); 109 | tunnels.forEach(tunnel -> { 110 | if (ServerChannelGroup.tunnelExists(tunnel)) { 111 | return; 112 | } 113 | new Thread(() -> { 114 | ProxyNettyServer proxyNettyServer = new ProxyNettyServer(); 115 | proxyNettyServer.start(tunnel.getServerPort()); 116 | proxyNettyServer.getNioServerFuture().addListener((future) -> { 117 | if (future.isSuccess()) { 118 | //端口监听创建成功后,缓存proxyNettyServer和tunnel信息 119 | ServerChannelGroup.addProxyServers(proxyNettyServer.getNioServerFuture().channel(),tunnel); 120 | log.debug("成功创建tunnel:[serverPort:{}], [ClientHost{}], [ClientPort:{}]", 121 | tunnel.getServerPort(), tunnel.getClientHost(), tunnel.getClientPort()); 122 | } 123 | }); 124 | }).start(); 125 | }); 126 | ctx.writeAndFlush(response); 127 | } 128 | 129 | /** 130 | * processor对应的命令字适配 131 | * @param cmd 132 | * @return 133 | */ 134 | @Override 135 | public boolean supply(byte cmd) { 136 | return Objects.equals(ProcessorEnum.LOGIN.getCmd(), cmd); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/ClientChannelGroup.java: -------------------------------------------------------------------------------- 1 | package core.netty.group; 2 | 3 | import core.entity.Tunnel; 4 | import core.properties.cache.PropertiesCache; 5 | import core.netty.group.channel.strategy.KeyBasedForkStrategy; 6 | import core.netty.group.channel.strategy.StrategyManager; 7 | import core.netty.group.channel.strategy.constant.ForkStrategyEnum; 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelId; 10 | import io.netty.channel.group.ChannelGroup; 11 | import io.netty.channel.group.DefaultChannelGroup; 12 | import io.netty.util.concurrent.GlobalEventExecutor; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | import java.util.Map; 16 | import java.util.Objects; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | 19 | /** 20 | * @Author wneck130@gmail.com 21 | * @function 客户端连接组,管理所有客户端的TCP连接 22 | */ 23 | @Slf4j 24 | public class ClientChannelGroup { 25 | 26 | /** 27 | * 隧道信息,key为serverPort,value为tunnel完整信息 28 | */ 29 | private static Map tunnels = new ConcurrentHashMap<>(); 30 | 31 | /** 32 | * 添加tunnel 33 | * @param serverPort 34 | * @param tunnel 35 | */ 36 | public static void addTunnel(Integer serverPort, Tunnel tunnel) { 37 | tunnels.put(serverPort, tunnel); 38 | } 39 | 40 | /** 41 | * 查找tunnel 42 | * @param serverPort 43 | * @return 44 | */ 45 | public static Tunnel getTunnel(Integer serverPort) { 46 | return tunnels.get(serverPort); 47 | } 48 | 49 | /** 50 | * channel配对关系,key为server端channelId,value为client端channelId 51 | * proxyServerChannel与proxyClientChannel配对 52 | * internalServerChannel与internalClientChannel配对 53 | */ 54 | private static Map channelPair = new ConcurrentHashMap<>(); 55 | 56 | public static void addChannelPair(String serverChannelId, String clientChannelId) { 57 | channelPair.put(serverChannelId, clientChannelId); 58 | } 59 | 60 | public static void removeChannelPair(String serverChannelId, String clientChannelId) throws Exception{ 61 | channelPair.remove(serverChannelId, clientChannelId); 62 | } 63 | 64 | public static String getServerChannel(String clientChannelId) { 65 | String serverChannelId = ""; 66 | if (!channelPair.containsValue(clientChannelId)) { 67 | return serverChannelId; 68 | } 69 | for(Map.Entry entry : channelPair.entrySet()) { 70 | if (Objects.equals(entry.getValue(), clientChannelId)) { 71 | serverChannelId = entry.getKey(); 72 | } 73 | } 74 | return serverChannelId; 75 | } 76 | 77 | /** 78 | * 判断当前channel是否已经建立channelPair,没有建立channelPair的channel无法进行转发 79 | * 80 | * @param channelId 81 | * @return 82 | */ 83 | public static boolean channelPairExist(ChannelId channelId) { 84 | if (channelPair.isEmpty()) { 85 | return Boolean.FALSE; 86 | } 87 | if (channelPair.containsKey(channelId)) { 88 | return Boolean.TRUE; 89 | } 90 | if (channelPair.containsValue(channelId)) { 91 | return Boolean.TRUE; 92 | } 93 | return Boolean.FALSE; 94 | } 95 | 96 | /** 97 | * 从连接池中取出一条连接 98 | * @param forkStrategyEnum 获取连接的策略 99 | * @throws Exception 100 | */ 101 | public static synchronized Channel forkChannel(ForkStrategyEnum forkStrategyEnum) throws Exception{ 102 | return StrategyManager.getInstance(forkStrategyEnum.getClazz()).fork(internalGroup); 103 | } 104 | 105 | /** 106 | * 从连接池中取出一条连接 107 | * @param basedKey 基于key值的channel获取策略,key值一般可以直接使用channelId,当使用此策略时,同一业务的数据具有顺序性 108 | * @throws Exception 109 | */ 110 | public static synchronized Channel forkChannel(String basedKey) throws Exception{ 111 | KeyBasedForkStrategy keyBasedForkStrategy = (KeyBasedForkStrategy)StrategyManager 112 | .getInstance(ForkStrategyEnum.KEY.getClazz()); 113 | keyBasedForkStrategy.setKey(basedKey); 114 | return keyBasedForkStrategy.fork(internalGroup); 115 | } 116 | 117 | /** 118 | * 被代理服务的channel组 119 | */ 120 | private static Map proxyGroup = new ConcurrentHashMap<>(); 121 | 122 | /** 123 | * 代理程序添加channel 124 | * @param channel 125 | */ 126 | public static void addProxy(Channel channel) { 127 | String channelId = channel.id().asShortText(); 128 | proxyGroup.put(channelId, channel); 129 | } 130 | 131 | /** 132 | * 代理程序查找channel 133 | * @param channelId 134 | * @return 135 | */ 136 | public static Channel findProxy(String channelId) { 137 | return proxyGroup.get(channelId); 138 | } 139 | 140 | /** 141 | * 代理程序删除channel 142 | * @param channel 143 | * @return 144 | */ 145 | public static boolean removeProxy(Channel channel) { 146 | String channelId = channel.id().asShortText(); 147 | return proxyGroup.remove(channelId, channel); 148 | } 149 | 150 | /** 151 | * 系统内部连接池的channel组 152 | */ 153 | public static ChannelGroup internalGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 154 | 155 | public static void addInternal(Channel channel) { 156 | internalGroup.add(channel); 157 | } 158 | 159 | public static boolean removeInternal(Channel channel) { 160 | return internalGroup.remove(channel); 161 | } 162 | 163 | 164 | 165 | public static void printGroupState() { 166 | log.debug("当前channel情况:"); 167 | log.debug("proxyChannel数量:" + ClientChannelGroup.proxyGroup.size()); 168 | log.debug("InternalChannel数量:" + ClientChannelGroup.internalGroup.size()); 169 | log.debug("当前channelPair:"); 170 | ClientChannelGroup.channelPair.entrySet().stream().forEach((entry) -> { 171 | log.debug("[InternalChannel:" + entry.getKey() + ", ProxyChannel:" + entry.getValue() + "]"); 172 | }); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/internal/InternalNettyServer.java: -------------------------------------------------------------------------------- 1 | package server.internal; 2 | 3 | import core.netty.group.channel.message.MessageContext; 4 | import core.netty.handler.processor.ProcessorManager; 5 | import core.properties.cache.PropertiesCache; 6 | import core.constant.FrameConstant; 7 | import core.properties.loader.PropertiesLoader; 8 | import core.netty.stater.server.BaseServer; 9 | import core.netty.stater.server.NettyServer; 10 | import core.properties.loader.YamlLoader; 11 | import io.netty.bootstrap.ServerBootstrap; 12 | import io.netty.channel.ChannelInitializer; 13 | import io.netty.channel.ChannelOption; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.SocketChannel; 16 | import io.netty.channel.socket.nio.NioServerSocketChannel; 17 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 18 | import io.netty.handler.timeout.IdleStateHandler; 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.apache.logging.log4j.LogManager; 21 | import org.apache.logging.log4j.Logger; 22 | import server.internal.decoder.ByteToPojoDecoder; 23 | import server.internal.decoder.PojoToByteEncoder; 24 | import server.internal.handler.CustomEventHandler; 25 | import server.internal.handler.InternalServerHandler; 26 | import core.netty.handler.MessageSendFilter; 27 | import core.netty.handler.MessageReceiveFilter; 28 | import server.internal.handler.processor.constant.ProcessorEnum; 29 | 30 | import java.io.File; 31 | import java.net.URL; 32 | import java.util.Arrays; 33 | import java.util.Optional; 34 | import java.util.concurrent.TimeUnit; 35 | 36 | /** 37 | * @author wneck130@gmail.com 38 | * @Description: 39 | * @date 2021/9/29 40 | */ 41 | @Slf4j 42 | public class InternalNettyServer extends BaseServer implements NettyServer { 43 | 44 | /** 45 | * 心跳超时时间,默认为2倍心跳发送间隔,心跳超时后服务主动回收连接 46 | */ 47 | private static long HEARTBEAT_TIMEOUT = 2 * 1L; 48 | 49 | /** 50 | * 代理程序对外开发的端口 51 | */ 52 | private static String PORT = "internal.server.port"; 53 | 54 | public static void main(String[] args) throws Exception { 55 | InternalNettyServer internalNettyServer = new InternalNettyServer(); 56 | URL resources = internalNettyServer.getClass().getResource("/"); 57 | // 根据运行方式获取配置文件根路径 58 | new YamlLoader("properties.yml").load(Optional.ofNullable(resources) 59 | .filter(res -> "file".equalsIgnoreCase(res.getProtocol())) 60 | .map(URL::getPath).orElse(System.getProperty("user.dir"))); 61 | int port = PropertiesCache.getInstance().getInt(PORT); 62 | internalNettyServer.start(port); 63 | } 64 | 65 | @Override 66 | public void init() { 67 | cache = PropertiesCache.getInstance(); 68 | genericFutureListener = (future) -> { 69 | //启动成功则为每个命令注册响应监听器 70 | if (future.isSuccess()) { 71 | MessageContext messageContext = MessageContext.getInstance(); 72 | //将所有指令的处理器都指派给messageContext作为监听器 73 | Arrays.stream(ProcessorEnum.values()).forEach(processorEnum -> { 74 | messageContext.addResponseListener(ProcessorManager.getInstance(processorEnum.getClazz())); 75 | }); 76 | } else { 77 | //启动不成功在一定的延迟之后进行重启 78 | nioServerFuture.channel().eventLoop() 79 | .schedule(() -> this.start(port), 10, TimeUnit.SECONDS); 80 | } 81 | }; 82 | addShutdownHook(); 83 | } 84 | 85 | @Override 86 | public void start(int port) { 87 | init(); 88 | this.port = port; 89 | //如果已经启动了proxyServer,静默 90 | if (nioServerFuture != null && nioServerFuture.channel().isOpen()) { 91 | return; 92 | } 93 | try { 94 | bossGroup = new NioEventLoopGroup(FrameConstant.BOSSGROUP_NUM); 95 | workerGroup = new NioEventLoopGroup(); 96 | ServerBootstrap b = new ServerBootstrap(); 97 | ChannelInitializer channelInit = new ChannelInitializer() { 98 | @Override 99 | protected void initChannel(SocketChannel ch) throws Exception { 100 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(FrameConstant.FRAME_MAX_BYTES, 101 | FrameConstant.FRAME_LEN_INDEX, FrameConstant.FRAME_LEN_LEN)) 102 | .addLast(new IdleStateHandler(0, 0, HEARTBEAT_TIMEOUT, TimeUnit.MINUTES)) 103 | .addLast(new ByteToPojoDecoder()) 104 | .addLast(new PojoToByteEncoder()) 105 | .addLast(new MessageReceiveFilter()) 106 | .addLast(new MessageSendFilter()) 107 | .addLast(new CustomEventHandler()) 108 | .addLast(new InternalServerHandler()); 109 | } 110 | }; 111 | b.group(bossGroup, workerGroup) 112 | .channel(NioServerSocketChannel.class) 113 | .option(ChannelOption.SO_BACKLOG, FrameConstant.TCP_SO_BACKLOG) 114 | .option(ChannelOption.SO_REUSEADDR, FrameConstant.TCP_REUSE_ADDR) 115 | .childOption(ChannelOption.TCP_NODELAY, FrameConstant.TCP_NODELAY) 116 | .childHandler(channelInit) 117 | .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE); 118 | 119 | log.error("InternalServer started on port {}......", cache.getInt(PORT)); 120 | nioServerFuture = b.bind(cache.getInt(PORT)).sync(); 121 | //添加关闭重启的监听器,3秒后尝试重启 122 | nioServerFuture.addListener(genericFutureListener); 123 | nioServerFuture.channel().closeFuture().sync(); 124 | } catch (Exception e) { 125 | e.printStackTrace(); 126 | log.error("InternalServer started failed while listening on port {}!!!", cache.getInt(PORT), e); 127 | } 128 | } 129 | 130 | @Override 131 | public boolean isHeathy() { 132 | return nioServerFuture.channel().isOpen(); 133 | } 134 | 135 | @Override 136 | public boolean isRunning() { 137 | return nioServerFuture.channel().isActive(); 138 | } 139 | 140 | @Override 141 | public void close() { 142 | nioServerFuture.removeListener(genericFutureListener); 143 | nioServerFuture.channel().close(); 144 | log.info("ProxyServer closed success"); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/netty/group/ServerChannelGroup.java: -------------------------------------------------------------------------------- 1 | package core.netty.group; 2 | 3 | import core.entity.Tunnel; 4 | import core.netty.group.channel.strategy.KeyBasedForkStrategy; 5 | import core.netty.group.channel.strategy.StrategyManager; 6 | import core.netty.group.channel.strategy.constant.ForkStrategyEnum; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelId; 9 | import io.netty.channel.group.ChannelGroup; 10 | import io.netty.channel.group.DefaultChannelGroup; 11 | import io.netty.util.concurrent.GlobalEventExecutor; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.Objects; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | import java.util.concurrent.ScheduledFuture; 19 | 20 | /** 21 | * @Author wneck130@gmail.com 22 | * @Function 服务端channel组管理类 23 | */ 24 | @Slf4j 25 | public class ServerChannelGroup { 26 | 27 | /** 28 | * 缓存的proxyServer对象,每一个proxyServer对应监听了目标端口的NioServerSocket服务 29 | * key为处理netty服务端连接的NioServerSocketChannel, value为对应的隧道信息 30 | */ 31 | public static Map proxyServers = new HashMap<>(); 32 | 33 | /** 34 | * proxyServers对象的add操作方法 35 | * @param nioServerSocketChannel 36 | * @param tunnel 37 | */ 38 | public static void addProxyServers(Channel nioServerSocketChannel, Tunnel tunnel) { 39 | proxyServers.put(nioServerSocketChannel, tunnel); 40 | } 41 | 42 | /** 43 | * 根据nioServerSocketChannel获取对应tunnelId,用于服务端向客户端发送预创建连接请求 44 | * @param nioServerSocketChannel 45 | * @return 46 | */ 47 | public static Tunnel getTunnelByChannel(Channel nioServerSocketChannel) { 48 | return proxyServers.get(nioServerSocketChannel); 49 | } 50 | 51 | /** 52 | * 检查proxyServer是否重复开启 53 | * @param tunnel 54 | * @return 55 | */ 56 | public static boolean tunnelExists(Tunnel tunnel) { 57 | for (Map.Entry entry : proxyServers.entrySet()) { 58 | if (entry.getValue().getServerPort() == tunnel.getServerPort()) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | /** 66 | * proxyChannel每次发送数据都会期待在timeout时间内收到响应,如果超时则认为连接异常,断开proxyChannel重新服务 67 | * ScheduledFuture为当前channel对应eventloop中的一个定义关闭任务,超时后关闭连接 68 | */ 69 | private static Map futureMap = new ConcurrentHashMap<>(); 70 | 71 | /** 72 | * 被代理服务的channel组 73 | */ 74 | private static Map proxyGroup = new ConcurrentHashMap<>(); 75 | 76 | /** 77 | * 代理程序添加channel 78 | * @param channel 79 | */ 80 | public static void addProxy(Channel channel) { 81 | String channelId = channel.id().asShortText(); 82 | proxyGroup.put(channelId, channel); 83 | } 84 | 85 | /** 86 | * 代理程序查找channel 87 | * @param channelId 88 | * @return 89 | */ 90 | public static Channel findProxy(String channelId) { 91 | return proxyGroup.get(channelId); 92 | } 93 | 94 | /** 95 | * 代理程序删除channel 96 | * @param channel 97 | * @return 98 | */ 99 | public static boolean removeProxy(Channel channel) { 100 | String channelId = channel.id().asShortText(); 101 | return proxyGroup.remove(channelId, channel); 102 | } 103 | 104 | 105 | /** 106 | * 内部服务的channel组 107 | */ 108 | private static ChannelGroup internalGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 109 | 110 | public static void addInternal(Channel channel) { 111 | internalGroup.add(channel); 112 | } 113 | 114 | public static boolean removeInternal(Channel channel) { 115 | return internalGroup.remove(channel); 116 | } 117 | 118 | 119 | /** 120 | * channel配对关系,key为server端channelId,value为client端channelId 121 | * proxyServerChannel与proxyClientChannel配对 122 | * internalServerChannel与internalClientChannel配对 123 | */ 124 | private static Map channelPair = new ConcurrentHashMap<>(); 125 | 126 | 127 | /** 128 | * 从连接池中取出一条连接 129 | * @param forkStrategyEnum 获取连接的策略 130 | * @throws Exception 131 | */ 132 | public static synchronized Channel forkChannel(ForkStrategyEnum forkStrategyEnum) throws Exception{ 133 | return StrategyManager.getInstance(forkStrategyEnum.getClazz()).fork(internalGroup); 134 | } 135 | 136 | /** 137 | * 从连接池中取出一条连接 138 | * @param basedKey 基于key值的channel获取策略,key值一般可以直接使用channelId,当使用此策略时,同一业务的数据具有顺序性 139 | * @throws Exception 140 | */ 141 | public static synchronized Channel forkChannel(String basedKey) throws Exception{ 142 | KeyBasedForkStrategy keyBasedForkStrategy = (KeyBasedForkStrategy)StrategyManager 143 | .getInstance(ForkStrategyEnum.KEY.getClazz()); 144 | keyBasedForkStrategy.setKey(basedKey); 145 | return keyBasedForkStrategy.fork(internalGroup); 146 | } 147 | 148 | public static void addChannelPair(String serverChannelId, String clientChannelId) { 149 | channelPair.put(serverChannelId, clientChannelId); 150 | } 151 | 152 | /** 153 | * 根据serverChannelId获取配对的clientChannelId 154 | * @param serverChannelId 155 | * @return 156 | */ 157 | public static String getClientByServer(String serverChannelId) { 158 | String clientChannelId = ""; 159 | if (channelPair.containsKey(serverChannelId)) { 160 | clientChannelId = channelPair.get(serverChannelId); 161 | } 162 | return clientChannelId; 163 | } 164 | 165 | public static void removeChannelPair(Channel serverChannel, Channel clientChannel) throws Exception{ 166 | channelPair.remove(serverChannel, clientChannel); 167 | } 168 | 169 | /** 170 | * 判断当前channel是否已经建立channelPair,没有建立channelPair的channel无法进行转发 171 | * @param channel 172 | * @return 173 | */ 174 | public static boolean channelPairExist(Channel channel) { 175 | if (channelPair.isEmpty()) { 176 | return Boolean.FALSE; 177 | } 178 | if (channelPair.containsKey(channel)) { 179 | return Boolean.TRUE; 180 | } 181 | if (channelPair.containsValue(channel)) { 182 | return Boolean.TRUE; 183 | } 184 | return Boolean.FALSE; 185 | } 186 | 187 | 188 | 189 | /** 190 | * 打印当前channelGroup情况 191 | */ 192 | public static void printGroupState() { 193 | log.info("当前channel情况:\r\n" 194 | + "ProxyChannel数量:" + ServerChannelGroup.proxyGroup.size() + "\r\n" 195 | + "InternalChannel数量:" + ServerChannelGroup.internalGroup.size() + "\r\n" 196 | + "当前channelPair:\r\n"); 197 | ServerChannelGroup.channelPair 198 | .forEach((key, value) -> log.debug("[ServerChannel:" + key + ", ClientChannel:" + value + "]\r\n")); 199 | } 200 | 201 | /** 202 | * 存储超时关闭的定时任务,如果已经存在任务,则cancel新任务,保持原任务不变 203 | * @param channelId 204 | * @param future 205 | */ 206 | public static void addFuture(ChannelId channelId, ScheduledFuture future) { 207 | try { 208 | ScheduledFuture successOne = futureMap.putIfAbsent(channelId, future); 209 | if (!Objects.equals(successOne, future)) { 210 | future.cancel(true); 211 | } 212 | } catch (Exception e) { 213 | e.printStackTrace(); 214 | log.error("超时关闭proxyChannel异常!!!", e); 215 | } 216 | } 217 | 218 | public static void cancelFuture(ChannelId channelId) { 219 | try { 220 | futureMap.get(channelId).cancel(true); 221 | } catch (Exception e) { 222 | e.printStackTrace(); 223 | log.error("关闭ScheduledFuture任务异常!!!", e); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /nat-core/src/main/java/core/utils/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package core.utils; 2 | 3 | /** 4 | * 5 | * @author wneck130@gmail.com 6 | */ 7 | public final class ByteUtil { 8 | 9 | /** 10 | * The left is Bit7, the right is Bit0 11 | * @param b 12 | * @param position 7-0 from left to right 13 | * @param value 14 | * @return 15 | */ 16 | public static byte setBit(byte b, int position, boolean value){ 17 | int op = 1 << position; 18 | int temp = 0; 19 | if(value){ 20 | temp = b | op; 21 | }else{ 22 | op = ~op; 23 | temp = b & op; 24 | } 25 | return (byte)temp; 26 | } 27 | 28 | /** 29 | * The left is Bit7, the right is Bit0 30 | * @param b 31 | * @param position 7-0 from left to right 32 | * @return 33 | */ 34 | public static boolean getBit(byte b, int position){ 35 | String s = toBinaryString(b); 36 | char c = s.charAt(7 - position); 37 | return c=='1'; 38 | } 39 | 40 | public static String toBinaryString(byte b){ 41 | String s = Integer.toBinaryString(b & 0xFF); 42 | int len = s.length(); 43 | if(len < 8){ 44 | int offset = 8 - len; 45 | for(int j=0; j> 24) & 0xFF); 115 | result[1] = (byte) ((x >> 16) & 0xFF); 116 | result[2] = (byte) ((x >> 8) & 0xFF); 117 | result[3] = (byte) (x & 0xFF); 118 | return result; 119 | } 120 | 121 | public static int toInt(byte[] bytes){ 122 | int value = 0; 123 | for (int i=0; i<4; i++) { 124 | int shift = (4 - 1 - i) * 8; 125 | value += (bytes[i] & 0xFF) << shift; 126 | } 127 | return value; 128 | } 129 | 130 | public static int toInt(byte byteVal){ 131 | return byteVal & 0xFF; 132 | } 133 | 134 | public static byte[] fromShort(short x){ 135 | byte[] result = new byte[2]; 136 | result[0] = (byte) ((x >> 8) & 0xFF); 137 | result[1] = (byte) (x & 0xFF); 138 | return result; 139 | } 140 | 141 | public static short toShort(byte[] bytes){ 142 | short value = 0; 143 | for (int i=0; i<2; i++) { 144 | int shift = (2 - 1 - i) * 8; 145 | value += (bytes[i] & 0xFF) << shift; 146 | } 147 | return value; 148 | } 149 | 150 | public static byte[] fromLong(long x){ 151 | byte[] result = new byte[8]; 152 | result[0] = (byte) ((x >> 56) & 0xFF); 153 | result[1] = (byte) ((x >> 48) & 0xFF); 154 | result[2] = (byte) ((x >> 40) & 0xFF); 155 | result[3] = (byte) ((x >> 32) & 0xFF); 156 | result[4] = (byte) ((x >> 24) & 0xFF); 157 | result[5] = (byte) ((x >> 16) & 0xFF); 158 | result[6] = (byte) ((x >> 8) & 0xFF); 159 | result[7] = (byte) (x & 0xFF); 160 | return result; 161 | } 162 | 163 | public static long toLong(byte[] bytes){ 164 | long value = 0; 165 | for (int i=0; i<8; i++) { 166 | int shift = (8 - 1 - i) * 8; 167 | value += ( (long)(bytes[i] & 0xFF) ) << shift; 168 | } 169 | return value; 170 | } 171 | 172 | public static byte[] fromFloat(float x){ 173 | int i = Float.floatToIntBits(x); 174 | return fromInt(i); 175 | } 176 | 177 | public static float toFloat(byte[] bytes){ 178 | int i = toInt(bytes); 179 | return Float.intBitsToFloat(i); 180 | } 181 | 182 | public static byte[] fromDouble(double x){ 183 | long l = Double.doubleToRawLongBits(x); 184 | return fromLong(l); 185 | } 186 | 187 | public static double toDouble(byte[] bytes){ 188 | long l = toLong(bytes); 189 | return Double.longBitsToDouble(l); 190 | } 191 | 192 | public static String toHexString(byte[] bytes,String prefix,String suffix){ 193 | if(isEmpty(bytes)){ 194 | return null; 195 | } 196 | StringBuilder sb = new StringBuilder(); 197 | for(byte b : bytes){ 198 | sb.append(toHexString(b,prefix,suffix)); 199 | } 200 | return sb.toString(); 201 | } 202 | public static String toHexString(byte[] bytes){ 203 | if(isEmpty(bytes)){ 204 | return null; 205 | } 206 | StringBuilder sb = new StringBuilder(); 207 | for(byte b : bytes){ 208 | sb.append(toHexString(b)); 209 | } 210 | return sb.toString(); 211 | } 212 | public static byte[] parseHexStringToArray(String s){ 213 | if(StringUtil.isEmpty(s)){ 214 | return null; 215 | } 216 | int len = s.length(); 217 | if(len == 1){ 218 | byte[] tmp = new byte[1]; 219 | tmp[0] = parseHexString(s); 220 | return tmp; 221 | } 222 | if(len % 2 !=0){ 223 | return null; 224 | } 225 | int size = len / 2; 226 | byte[] data = new byte[size]; 227 | for(int i=0; i "file".equalsIgnoreCase(res.getProtocol())) 77 | .map(URL::getPath).orElse(System.getProperty("user.dir"))); 78 | internalClient.start(); 79 | ClientChannelGroup.printGroupState(); 80 | } 81 | 82 | @Override 83 | public void init() { 84 | cache = PropertiesCache.getInstance(); 85 | this.host = cache.get(HOST); 86 | this.port = cache.getInt(PORT); 87 | group = new NioEventLoopGroup(); 88 | //定义线程组,处理读写和链接事件 89 | client.group(group) 90 | .channel(NioSocketChannel.class) 91 | .handler(new ChannelInitializer() { 92 | @Override 93 | protected void initChannel(NioSocketChannel ch) { 94 | ch.pipeline().addFirst(new LengthFieldBasedFrameDecoder(FrameConstant.FRAME_MAX_BYTES, 95 | FrameConstant.FRAME_LEN_INDEX, FrameConstant.FRAME_LEN_LEN)) 96 | .addLast(new IdleStateHandler(0, 0, HEARTBEAT_INTERVAL, TimeUnit.MINUTES)) 97 | .addLast(new ByteToPojoDecoder()) 98 | .addLast(new PojoToByteEncoder()) 99 | .addLast(new MessageReceiveFilter()) 100 | .addLast(new MessageSendFilter()) 101 | .addLast(new CustomEventHandler()) 102 | .addLast(new InternalClientHandler()); 103 | } 104 | }); 105 | } 106 | 107 | @Override 108 | public void start() { 109 | init(); 110 | initNum = cache.getInt(INIT_NUM); 111 | connect(initNum, host, port); 112 | } 113 | 114 | /** 115 | * 启动指定数量的内部的连接 116 | * 建立连接失败时,按照简单的延迟重连机制进行重连 117 | * 118 | * @throws Exception 119 | */ 120 | public void connect(Integer num, String host, int port) { 121 | //连接服务器 122 | if (ClientChannelGroup.internalGroup.size() < num) { 123 | try { 124 | client.connect(host,port).sync().addListener((future -> { 125 | if (future.isSuccess()) { 126 | connect(num, host, port); 127 | CONNECTION_DELAY = 1; 128 | } else { 129 | BaseClient.scheduledExecutor.schedule(() -> connect(num, host, port), CONNECTION_DELAY, TimeUnit.SECONDS); 130 | CONNECTION_DELAY = CONNECTION_DELAY * 2; 131 | log.error("启动internalClient失败," + CONNECTION_DELAY + "秒后重试!!!"); 132 | } 133 | })); 134 | } catch (Exception e) { 135 | e.printStackTrace(); 136 | log.error("connect to InternalSever error : ", e); 137 | BaseClient.scheduledExecutor.schedule(() -> connect(num, host, port), CONNECTION_DELAY, TimeUnit.SECONDS); 138 | CONNECTION_DELAY = CONNECTION_DELAY * 2; 139 | log.error("启动internalClient失败," + CONNECTION_DELAY + "秒后重试!!!"); 140 | } 141 | } else { 142 | //连接池启动完毕后开始注册隧道信息 143 | // 多次注册不会影响服务端的端口监听,防止有新增加的隧道信息需要注册,每次连接都重新同步隧道信息 144 | register(); 145 | } 146 | 147 | } 148 | 149 | /** 150 | * 向服务端发送隧道注册信息 151 | */ 152 | public void register() { 153 | //指定数量的连接创建完毕,向服务端注册tunnel信息 154 | try { 155 | //获取tunnel信息 156 | List tunnels = cache.getList("tunnel"); 157 | for (int i = 0; i < tunnels.size(); i++) { 158 | LinkedHashMap tunnelMap = (LinkedHashMap) tunnels.get(i); 159 | Tunnel tunnel = new Tunnel(); 160 | Integer serverPort = (Integer) tunnelMap.get("serverPort"); 161 | tunnel.setServerPort(serverPort); 162 | tunnel.setClientHost((String) tunnelMap.get("clientHost")); 163 | tunnel.setClientPort((Integer) tunnelMap.get("clientPort")); 164 | ClientChannelGroup.addTunnel(serverPort, tunnel); 165 | 166 | Channel channel = ClientChannelGroup.forkChannel(ForkStrategyEnum.RANDOM); 167 | 168 | Frame frame = new Frame(); 169 | frame.setReq(channel.id().asShortText()); 170 | frame.setRes(FrameConstant.DEFAULT_CHANNEL_ID); 171 | frame.setCmd(ProcessorEnum.LOGIN.getCmd()); 172 | LinkedHashMap data = new LinkedHashMap<>(); 173 | String password = cache.get("password"); 174 | data.put("passwordLen", ByteUtil.fromInt(password.length())[3]); 175 | data.put("password", password.getBytes(StandardCharsets.UTF_8)); 176 | data.put("serverPort", new byte[]{ByteUtil.fromInt(tunnel.getServerPort())[2], 177 | ByteUtil.fromInt(tunnel.getServerPort())[3]}); 178 | String[] clientHostSegment = tunnel.getClientHost().split("\\."); 179 | data.put("clientHost", new byte[]{ByteUtil.fromInt(Integer.parseInt(clientHostSegment[0]))[3], 180 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[1]))[3], 181 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[2]))[3], 182 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[3]))[3]}); 183 | data.put("clientPort", new byte[]{ByteUtil.fromInt(tunnel.getClientPort())[2], 184 | ByteUtil.fromInt(tunnel.getClientPort())[3]}); 185 | frame.setData(data); 186 | channel.writeAndFlush(frame); 187 | } 188 | } catch (Exception e) { 189 | log.error("向服务端注册隧道失败!!!"); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /nat-server/src/main/java/server/proxy/ProxyNettyServer.java: -------------------------------------------------------------------------------- 1 | package server.proxy; 2 | 3 | import core.entity.Frame; 4 | import core.entity.Tunnel; 5 | import core.netty.group.ServerChannelGroup; 6 | import core.netty.group.channel.strategy.constant.ForkStrategyEnum; 7 | import core.properties.cache.PropertiesCache; 8 | import core.constant.FrameConstant; 9 | import core.netty.handler.DispatcherHandler; 10 | import core.netty.stater.server.BaseServer; 11 | import core.netty.stater.server.NettyServer; 12 | import core.utils.ByteUtil; 13 | import io.netty.bootstrap.ServerBootstrap; 14 | import io.netty.channel.Channel; 15 | import io.netty.channel.ChannelHandlerContext; 16 | import io.netty.channel.ChannelInitializer; 17 | import io.netty.channel.ChannelOption; 18 | import io.netty.channel.nio.NioEventLoopGroup; 19 | import io.netty.channel.socket.SocketChannel; 20 | import io.netty.channel.socket.nio.NioServerSocketChannel; 21 | import io.netty.handler.codec.http.HttpObjectAggregator; 22 | import io.netty.handler.codec.http.HttpServerCodec; 23 | import io.netty.handler.timeout.IdleStateHandler; 24 | import lombok.extern.slf4j.Slf4j; 25 | import server.internal.handler.processor.constant.ProcessorEnum; 26 | import server.proxy.handler.HttpProxyServerHandler; 27 | import server.proxy.handler.TcpInLogHandler; 28 | import server.proxy.handler.TcpOutLogHandler; 29 | import server.proxy.handler.TcpProxyServerHandler; 30 | 31 | import java.util.LinkedHashMap; 32 | import java.util.concurrent.TimeUnit; 33 | 34 | /** 35 | * @author wneck130@gmail.com 36 | * @Description: 37 | * @date 2021/9/26 38 | */ 39 | @Slf4j 40 | public class ProxyNettyServer extends BaseServer implements NettyServer { 41 | 42 | @Override 43 | public void init() { 44 | cache = PropertiesCache.getInstance(); 45 | genericFutureListener = (ch) -> { 46 | nioServerFuture.channel().eventLoop() 47 | .schedule(() -> this.start(port), 10, TimeUnit.SECONDS); 48 | }; 49 | addShutdownHook(); 50 | } 51 | 52 | @Override 53 | public void start(int port) { 54 | init(); 55 | this.port = port; 56 | //如果已经启动了proxyServer,静默 57 | if (nioServerFuture != null && nioServerFuture.channel().isOpen()) { 58 | return; 59 | } 60 | try { 61 | bossGroup = new NioEventLoopGroup(FrameConstant.BOSSGROUP_NUM); 62 | workerGroup = new NioEventLoopGroup(); 63 | ServerBootstrap b = new ServerBootstrap(); 64 | ChannelInitializer channelInit = new ChannelInitializer() { 65 | @Override 66 | protected void initChannel(SocketChannel ch) throws Exception { 67 | ch.pipeline() 68 | //闲置连接回收 69 | .addLast(new IdleStateHandler(READ_IDLE, WRITE_IDLE, ALL_IDLE, TimeUnit.MINUTES)) 70 | //inbound流日志记录 71 | .addLast(new TcpInLogHandler()) 72 | //outbound流日志记录 73 | .addLast(new TcpOutLogHandler()) 74 | //业务处理 75 | // .addLast(new TcpProxyServerHandler()); 76 | .addLast("dispatcher", new DispatcherHandler() { 77 | /** 78 | * 根据传输的数据内容识别出TCP/HTTP协议类型后进行pipeline动态调整 79 | * @param ctx 80 | */ 81 | @Override 82 | public void addHttpHandler(ChannelHandlerContext ctx) { 83 | // server端发送的是httpResponse,所以要使用HttpResponseEncoder进行编码 84 | ch.pipeline().addLast(new HttpServerCodec()) 85 | .addLast(new HttpObjectAggregator(512 * 1024)) 86 | .addLast(new HttpProxyServerHandler()); 87 | } 88 | 89 | /** 90 | * 根据传输的数据内容识别出TCP/HTTP协议类型后进行pipeline动态调整 91 | * @param ctx 92 | */ 93 | @Override 94 | public void addTcpHandler(ChannelHandlerContext ctx) { 95 | ctx.pipeline() 96 | //闲置连接回收 97 | .addLast(new IdleStateHandler(READ_IDLE, WRITE_IDLE, ALL_IDLE, TimeUnit.MINUTES)) 98 | //inbound流日志记录 99 | .addLast(new TcpInLogHandler()) 100 | //outbound流日志记录 101 | .addLast(new TcpOutLogHandler()) 102 | //业务处理 103 | .addLast(new TcpProxyServerHandler()); 104 | } 105 | /** 106 | * 外部请求与ProxyServer激活channel连接时,通知ProxyClient与被代理服务预建立连接 107 | */ 108 | @Override 109 | public void fullyConnect(ChannelHandlerContext ctx) throws Exception { 110 | //发送启动代理客户端命令 111 | Frame frame = new Frame(); 112 | frame.setReq(ctx.channel().id().asShortText()); 113 | frame.setRes(FrameConstant.DEFAULT_CHANNEL_ID); 114 | frame.setCmd(ProcessorEnum.PRE_CONNECT.getCmd()); 115 | //通过当前channel的parent channel获取对应的tunnelId 116 | Tunnel tunnel = ServerChannelGroup.getTunnelByChannel(ctx.channel().parent()); 117 | Channel internalChannel = ServerChannelGroup.forkChannel(ForkStrategyEnum.MIN_LOAD); 118 | LinkedHashMap data = new LinkedHashMap<>(1); 119 | String[] clientHostSegment = tunnel.getClientHost().split("\\."); 120 | data.put("host", new byte[]{ByteUtil.fromInt(Integer.parseInt(clientHostSegment[0]))[3], 121 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[1]))[3], 122 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[2]))[3], 123 | ByteUtil.fromInt(Integer.parseInt(clientHostSegment[3]))[3]}); 124 | data.put("port", new byte[]{ByteUtil.fromInt(tunnel.getClientPort())[2], 125 | ByteUtil.fromInt(tunnel.getClientPort())[3]}); 126 | frame.setData(data); 127 | internalChannel.writeAndFlush(frame); 128 | } 129 | }); 130 | 131 | } 132 | }; 133 | b.group(bossGroup, workerGroup) 134 | .channel(NioServerSocketChannel.class) 135 | .option(ChannelOption.SO_BACKLOG, FrameConstant.TCP_SO_BACKLOG) 136 | .option(ChannelOption.SO_REUSEADDR, FrameConstant.TCP_REUSE_ADDR) 137 | .childHandler(channelInit) 138 | .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE); 139 | 140 | nioServerFuture = b.bind(port).sync(); 141 | log.debug("ProxyServer started on port {}......", port); 142 | //添加关闭重启的监听器,3秒后尝试重启 143 | nioServerFuture.channel().closeFuture().addListener(genericFutureListener); 144 | // nioServerFuture.channel().closeFuture().sync(); 145 | } catch (Exception e) { 146 | log.error("ProxyServer started failed while listening on port {}!!!", port, e); 147 | } 148 | } 149 | 150 | @Override 151 | public boolean isHeathy() { 152 | return nioServerFuture.channel().isOpen(); 153 | } 154 | 155 | @Override 156 | public boolean isRunning() { 157 | return nioServerFuture.channel().isActive(); 158 | } 159 | 160 | @Override 161 | public void close() { 162 | nioServerFuture.removeListener(genericFutureListener); 163 | nioServerFuture.channel().close(); 164 | log.info("ProxyServer closed success"); 165 | } 166 | } 167 | --------------------------------------------------------------------------------