diff --git a/.gitignore b/.gitignore index 993208fd..950efd68 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,6 @@ hs_err_pid* **/memo/** -neutrino-proxy-vuepress/deploy.sh \ No newline at end of file +neutrino-proxy-vuepress/deploy.sh +.NEUTRINO_PROXY_CLIENT_ID +logs diff --git a/README.md b/README.md index f00a9a1f..52e49b47 100644 --- a/README.md +++ b/README.md @@ -34,144 +34,19 @@ ![License管理](assets/run-example/license1.png) ![端口映射管理](assets/run-example/port-mapping1.png) -# 4、项目结构 -- neutrino-proxy - - neutrino-proxy-core 与代理相关的公共常量、编解码器 - - neutrino-proxy-client 代理客户端项目 - - neutrino-proxy-server 代理服务端项目 - - neutrino-proxy-admin 代理监控项目(基于vue-element-admin开发) - -# 5、运行 -## 5.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过 -```shell -keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks" -``` - -## 5.2、修改服务端配置(app.yml) -```yml -application: - name: neutrino-proxy-server - -neutrino: - proxy: - protocol: - max-frame-length: 2097152 - length-field-offset: 0 - length-field-length: 4 - initial-bytes-to-strip: 0 - length-adjustment: 0 - read-idle-time: 60 - write-idle-time: 40 - all-idle-time-seconds: 0 - tunnel: - boss-thread-count: 2 - work-thread-count: 10 - # 服务端端口,用于保持与客户端的连接,非SSL - port: ${OPEN_PORT:9000} - # 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置 - ssl-port: ${SSL_PORT:9002} - # 证书配置,用于隧道通信SSL加密 - key-store-password: ${STORE_PASS:123456} - key-manager-password: ${MGR_PASS:123456} - jks-path: ${JKS_PATH:classpath:/test.jks} - server: - boss-thread-count: 5 - work-thread-count: 20 - # HTTP代理端口,默认80,也可以用其他端口,走nginx转发 - http-proxy-port: ${HTTP_PROXY_PORT:80} - # HTTPS代理端口,默认443,也可以用其他端口,走nginx转发 - https-proxy-port: ${HTTPS_PROXY_PORT:443} - # 如果不配置,则不支持域名映射 - domain-name: ${DOMAIN_NAME:} - # 证书配置,用于支持HTTPS - key-store-password: ${HTTPS_STORE_PASS:} - jks-path: ${HTTPS_JKS_PATH:} - data: - # 数据库配置(支持mysql) - type: sqlite - url: jdbc:sqlite:data.db - driver-class: org.sqlite.JDBC - username: - password: -``` - -## 5.3、启动服务端 -> java -jar neutrino-proxy-server.jar - -## 5.4、修改客户端配置 -```yml -application: - name: neutrino-proxy-client - -proxy: - protocol: - max-frame-length: 2097152 - length-field-offset: 0 - length-field-length: 4 - initial-bytes-to-strip: 0 - length-adjustment: 0 - read-idle-time: 60 - write-idle-time: 30 - all-idle-time-seconds: 0 - client: - # ssl证书密码 - key-store-password: 123456 - # ssl证书存放位置 - jks-path: classpath:/test.jks - # 服务端ip,若部署到服务器,则配置服务器的ip - server-ip: localhost - # 服务端端口,若使用ssl,则需要配置为服务端的"ssl-port" - server-port: 9000 - # 是否启用ssl,启用则必须配置ssl相关参数 - ssl-enable: false -``` -## 5.5、代理示意图 +# 4、代理示意图 ![代理流程](assets/design/neutrino-proxy-process.jpg) -## 5.6、启动客户端 -### 5.6.1、启动参数直接指定配置 -> java -jar neutrino-proxy-client.jar serverIp=localhost serverPort=9000 licenseKey=b0a907332b474b25897c4dcb31fc7eb6 - -### 5.6.2、启动参数指定外部配置文件 -> java -jar neutrino-proxy-client.jar config=app.properties - -配置文件格式如下: -``` -neutrino.proxy.client.server-ip=localhost -neutrino.proxy.client.server-port=9002 -neutrino.proxy.client.ssl-enable=true -neutrino.proxy.client.key-store-password=123456 -neutrino.proxy.client.jks-path=classpath:/test.jks -neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6 -``` - -## 5.7、Docker快速启动 -```shell script - # 一键部署前端和后端,客户端按上方部署即可 - docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -d --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.64 - # 如没docker环境,可以一键部署docker环境(centos 7.8)系统 - curl http://www.wangke666.cn/static/createDocker.sh | bash -``` - -# 6、演示环境 +# 5、演示环境 > 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用! - 管理后台地址:http://103.163.47.16:9527 - 游客账号:visitor/123456 -# 7、未来迭代方向 -- 优化代码、增强稳定性 -- 服务端增加管理页面,提供报表、授权、限流等功能 -- 从项目中分离、孵化出另一个开源项目(neutrino-framework) - -# 8、技术文档 -- [Aop](./docs/Aop.MD) -- [Channel](./docs/Channel.MD) - -# 9、联系我们 +# 6、联系我们 - 微信: yuyunshize - Gitee: https://gitee.com/asgc/neutrino-proxy -# 10、贡献者列表 +# 7、贡献者列表

diff --git a/neutrino-proxy-admin/config/docker.env.js b/neutrino-proxy-admin/config/docker.env.js index 33aca667..3793cee6 100644 --- a/neutrino-proxy-admin/config/docker.env.js +++ b/neutrino-proxy-admin/config/docker.env.js @@ -1,5 +1,5 @@ module.exports = { NODE_ENV: '"development"', ENV_CONFIG: '"docker"', - BASE_API: '"/"' + BASE_API: '"/neutrino-proxy-server"' } diff --git a/neutrino-proxy-admin/src/views/proxy/portMapping.vue b/neutrino-proxy-admin/src/views/proxy/portMapping.vue index 285cca59..32d517e1 100644 --- a/neutrino-proxy-admin/src/views/proxy/portMapping.vue +++ b/neutrino-proxy-admin/src/views/proxy/portMapping.vue @@ -154,7 +154,7 @@ - + diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/ProxyClient.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/ProxyClient.java index c47f7a61..c01f343a 100644 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/ProxyClient.java +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/ProxyClient.java @@ -1,14 +1,22 @@ package org.dromara.neutrinoproxy.client; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.noear.solon.Solon; +import org.noear.solon.SolonApp; import org.noear.solon.annotation.SolonMain; +import org.slf4j.LoggerFactory; /** * * @author: aoshiguchen * @date: 2022/6/16 */ +@Slf4j @SolonMain public class ProxyClient { @@ -20,6 +28,8 @@ public class ProxyClient { setAlias("neutrino.proxy.client.jksPath", "jksPath"); setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword"); setAlias("neutrino.proxy.client.licenseKey", "licenseKey"); + // 设置日志级别 + setLogLevel(app); }); } @@ -34,4 +44,21 @@ public class ProxyClient { Solon.cfg().put(key, val); } } + + private static void setLogLevel(SolonApp app) { + String loggerLevel = app.cfg().get("neutrino.proxy.logger.level"); + if (StringUtils.isBlank(loggerLevel)) { + return; + } + + try { + Level level = Level.toLevel(loggerLevel); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + for (Logger logger : loggerContext.getLoggerList()) { + logger.setLevel(level); + } + } catch (Exception e) { + log.error("日志级别设置失败", e); + } + } } diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/CustomConfig.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/CustomConfig.java deleted file mode 100644 index 6f8de075..00000000 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/CustomConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2022 aoshiguchen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.dromara.neutrinoproxy.client.config; - -import lombok.Data; - -/** - * @author: aoshiguchen - * @date: 2022/10/18 - */ -@Data -public class CustomConfig { - private String jksPath; - private String serverIp; - private Integer serverPort; - private Boolean sslEnable; - private String licenseKey; -} diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfig.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfig.java index 20f48a18..68aa435b 100644 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfig.java +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfig.java @@ -39,5 +39,15 @@ public class ProxyConfig { private Integer obtainLicenseInterval; private String licenseKey; private Integer threadCount; + private String clientId; + private Boolean transferLogEnable; + private Boolean heartbeatLogEnable; + private Reconnection reconnection; + } + + @Data + public static class Reconnection { + private Integer intervalSeconds; + private Boolean unlimited; } } diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfiguration.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfiguration.java index ebc761ef..ca206083 100644 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfiguration.java +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/config/ProxyConfiguration.java @@ -31,8 +31,13 @@ public class ProxyConfiguration implements LifecycleBean { Solon.context().wrapAndPut(Dispatcher.class, dispatcher); } - @Bean("bootstrap") - public Bootstrap bootstrap() { + @Bean("cmdTunnelBootstrap") + public Bootstrap cmdTunnelBootstrap() { + return new Bootstrap(); + } + + @Bean("proxyTunnelBootstrap") + public Bootstrap proxyTunnelBootstrap() { return new Bootstrap(); } diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/CmdChannelHandler.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/CmdChannelHandler.java new file mode 100644 index 00000000..decd5c0b --- /dev/null +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/CmdChannelHandler.java @@ -0,0 +1,85 @@ +package org.dromara.neutrinoproxy.client.core; + +import org.dromara.neutrinoproxy.client.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.timeout.IdleStateEvent; +import lombok.extern.slf4j.Slf4j; +import org.noear.solon.Solon; + +/** + * 处理与服务端之间的数据传输 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +public class CmdChannelHandler extends SimpleChannelInboundHandler { + private static volatile Boolean transferLogEnable = Boolean.FALSE; + + public CmdChannelHandler() { + ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class); + if (null != proxyConfig.getClient() && null != proxyConfig.getClient().getHeartbeatLogEnable()) { + transferLogEnable = proxyConfig.getClient().getHeartbeatLogEnable(); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception { + if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) { + log.debug("Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType()); + } + Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get(); + if (realServerChannel != null) { + realServerChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable()); + } + + super.channelWritabilityChanged(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + log.info("Client CmdChannel 与服务端断开连接"); + ProxyUtil.setCmdChannel(null); + ProxyUtil.clearRealServerChannels(); + + super.channelInactive(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("Client CmdChannel Error channelId:{}", ctx.channel().id().asLongText(), cause); + ctx.close(); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if(evt instanceof IdleStateEvent) { + IdleStateEvent event = (IdleStateEvent)evt; + switch (event.state()) { + case READER_IDLE: + // 读超时,断开连接 + log.error("Client CmdChannel 读超时,断开连接"); + ctx.channel().close(); + break; + case WRITER_IDLE: + ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage()); + break; + case ALL_IDLE: + log.error("Client CmdChannel 读写超时,断开连接"); + ctx.close(); + break; + } + } + } +} diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/ClientChannelHandler.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/ProxyChannelHandler.java similarity index 72% rename from neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/ClientChannelHandler.java rename to neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/ProxyChannelHandler.java index b6d85001..85e7b0f0 100644 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/ClientChannelHandler.java +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/ProxyChannelHandler.java @@ -1,15 +1,15 @@ package org.dromara.neutrinoproxy.client.core; -import org.dromara.neutrinoproxy.client.util.ProxyUtil; -import org.dromara.neutrinoproxy.core.Constants; -import org.dromara.neutrinoproxy.core.ProxyMessage; -import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOption; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.timeout.IdleStateEvent; import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; import org.noear.solon.Solon; /** @@ -18,13 +18,13 @@ import org.noear.solon.Solon; * @date: 2022/6/16 */ @Slf4j -public class ClientChannelHandler extends SimpleChannelInboundHandler { +public class ProxyChannelHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception { if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) { - log.info("recieved proxy message, type is {}", proxyMessage.getType()); + log.debug("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType()); } Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage); } @@ -41,17 +41,10 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler() { @@ -78,9 +82,10 @@ public class ProxyClientService { } }); - bootstrap.group(workerGroup); - bootstrap.channel(NioSocketChannel.class); - bootstrap.handler(new ChannelInitializer() { + proxyTunnelBootstrap.group(workerGroup); + proxyTunnelBootstrap.channel(NioSocketChannel.class); + proxyTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())); + proxyTunnelBootstrap.handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { @@ -93,10 +98,46 @@ public class ProxyClientService { proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip())); ch.pipeline().addLast(new ProxyMessageEncoder()); ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds())); - ch.pipeline().addLast(new ClientChannelHandler()); + ch.pipeline().addLast(new ProxyChannelHandler()); } }); - this.start(); + + cmdTunnelBootstrap.group(workerGroup); + cmdTunnelBootstrap.channel(NioSocketChannel.class); +// cmdTunnelBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); +// cmdTunnelBootstrap.option(ChannelOption.SO_KEEPALIVE, true); +// /** +// * TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。 +// * Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。 +// */ +// cmdTunnelBootstrap.option(ChannelOption.TCP_NODELAY, true); + cmdTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())); + + cmdTunnelBootstrap.handler(new ChannelInitializer() { + + @Override + public void initChannel(SocketChannel ch) throws Exception { + if (proxyConfig.getClient().getSslEnable()) { + ch.pipeline().addLast(createSslHandler()); + } + if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.class)); + } + ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(), + proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(), + proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip())); + ch.pipeline().addLast(new ProxyMessageEncoder()); + ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds())); + ch.pipeline().addLast(new CmdChannelHandler()); + } + }); + + try { + this.start(); + } catch (Exception e) { + // 启动连不上也做一下重连,因此先catch异常 + log.error("[客户端指令隧道] 启动异常", e); + } } public void start() { @@ -111,7 +152,7 @@ public class ProxyClientService { return; } if (null != proxyConfig.getClient().getSslEnable() && proxyConfig.getClient().getSslEnable() - && StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) { + && StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) { log.error("not found jks-path config."); Solon.stop(); return; @@ -128,7 +169,7 @@ public class ProxyClientService { log.error("client start error", e); } } else { - channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey())); + channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId())); } } @@ -136,7 +177,7 @@ public class ProxyClientService { * 连接代理服务器 */ private void connectProxyServer() throws InterruptedException { - bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort()) + cmdTunnelBootstrap.connect() .addListener(new ChannelFutureListener() { @Override @@ -145,13 +186,13 @@ public class ProxyClientService { channel = future.channel(); // 连接成功,向服务器发送客户端认证信息(licenseKey) ProxyUtil.setCmdChannel(future.channel()); - future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey())); - log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText()); + future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId())); + log.info("[客户端指令隧道] 连接代理服务成功. channelId:{}", future.channel().id().asLongText()); - reconnectServiceEnable = true; +// reconnectServiceEnable = true; reconnectCount = 0; } else { - log.info("连接代理服务失败!"); + log.info("[客户端指令隧道] 连接代理服务失败!"); } } }).sync(); @@ -181,17 +222,21 @@ public class ProxyClientService { } protected synchronized void reconnect() { - if (!reconnectServiceEnable) { - return; +// if (!reconnectServiceEnable) { +// return; +// } + if (null != channel) { + if (channel.isActive()) { + return; + } + channel.close(); } - if (null != channel && channel.isActive()) { - return; - } - log.info("客户端重连 seq:{}", ++reconnectCount); + + log.info("[客户端指令隧道] 客户端重连 seq:{}", ++reconnectCount); try { connectProxyServer(); } catch (Exception e) { - log.error("重连异常", e); + log.error("[客户端指令隧道] 重连异常", e); } } } diff --git a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/RealServerChannelHandler.java b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/RealServerChannelHandler.java index 0506217b..69c47d45 100644 --- a/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/RealServerChannelHandler.java +++ b/neutrino-proxy-client/src/main/java/org/dromara/neutrinoproxy/client/core/RealServerChannelHandler.java @@ -22,6 +22,7 @@ package org.dromara.neutrinoproxy.client.core; +import lombok.extern.slf4j.Slf4j; import org.dromara.neutrinoproxy.client.util.ProxyUtil; import org.dromara.neutrinoproxy.core.Constants; import org.dromara.neutrinoproxy.core.ProxyMessage; @@ -36,6 +37,7 @@ import io.netty.channel.SimpleChannelInboundHandler; * @author: aoshiguchen * @date: 2022/6/16 */ +@Slf4j public class RealServerChannelHandler extends SimpleChannelInboundHandler { @@ -85,6 +87,6 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler { + proxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { borrowListener.success(future.channel()); } else { @@ -129,4 +134,24 @@ public class ProxyUtil { realServerChannels.clear(); } + + public static String getClientId() { + if (StringUtils.isNotBlank(clientId)) { + return clientId; + } + ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class); + if (StringUtils.isNotBlank(proxyConfig.getClient().getClientId())) { + clientId = proxyConfig.getClient().getClientId(); + return clientId; + } + String id = FileUtil.readContentAsString(CLIENT_ID_FILE); + if (StringUtils.isNotBlank(id)) { + clientId = id; + return id; + } + id = UUID.randomUUID().toString().replace("-", ""); + FileUtil.write(CLIENT_ID_FILE, id); + clientId = id; + return id; + } } diff --git a/neutrino-proxy-client/src/main/resources/app.yml b/neutrino-proxy-client/src/main/resources/app.yml index cef79537..23072516 100644 --- a/neutrino-proxy-client/src/main/resources/app.yml +++ b/neutrino-proxy-client/src/main/resources/app.yml @@ -9,15 +9,35 @@ neutrino: length-field-length: 4 initial-bytes-to-strip: 0 length-adjustment: 0 - read-idle-time: 40 - write-idle-time: 5 - all-idle-time-seconds: 45 + read-idle-time: 8 + write-idle-time: 3 + all-idle-time-seconds: 8 + logger: + # 日志级别 + level: ${LOG_LEVEL:info} client: + # 线程池相关配置,用于技术调优,可忽略 thread-count: 50 + # 隧道SSL证书配置 key-store-password: ${STORE_PASS:123456} jks-path: ${JKS_PATH:classpath:/test.jks} + # 服务端IP server-ip: ${SERVER_IP:localhost} + # 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port) server-port: ${SERVER_PORT:9002} + # 是否启用SSL(注意:该配置必须和server-port对应上) ssl-enable: ${SSL_ENABLE:true} - obtain-license-interval: 5 - license-key: ${LICENSE_KEY:b0a907332b474b25897c4dcb31fc7eb6} + # 客户端连接唯一凭证 + license-key: ${LICENSE_KEY:} + # 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成) + client-id: ${CLIENT_ID:} + # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${CLIENT_LOG:false} + # 是否开启心跳日志 + heartbeat-log-enable: ${HEARTBEAT_LOG:false} + # 重连设置 + reconnection: + # 重连间隔(秒) + interval-seconds: 10 + # 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启) + unlimited: false diff --git a/neutrino-proxy-client/src/main/resources/logback.xml b/neutrino-proxy-client/src/main/resources/logback.xml index e7178e77..76953690 100644 --- a/neutrino-proxy-client/src/main/resources/logback.xml +++ b/neutrino-proxy-client/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ExceptionEnum.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ExceptionEnum.java index d3315d7a..71cdc7a9 100644 --- a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ExceptionEnum.java +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ExceptionEnum.java @@ -35,7 +35,10 @@ import lombok.Getter; public enum ExceptionEnum { SUCCESS(0, "成功"), AUTH_FAILED(1, "认证失败"), - CONNECT_FAILED(2, "连接失败"); + CONNECT_FAILED(2, "连接失败"), + @Deprecated + LICENSE_CANNOT_REPEAT_CONNECT(3, "license不能多个客户端同时使用"), + ; private Integer code; private String msg; diff --git a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java index b4b0e299..5470565e 100644 --- a/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java +++ b/neutrino-proxy-core/src/main/java/org/dromara/neutrinoproxy/core/ProxyMessage.java @@ -100,9 +100,9 @@ public class ProxyMessage { return create().setType(TYPE_HEARTBEAT); } - public static ProxyMessage buildAuthMessage(String info) { + public static ProxyMessage buildAuthMessage(String info, String clientId) { return create().setType(TYPE_AUTH) - .setInfo(info); + .setInfo(info + "," + clientId); } public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) { diff --git a/neutrino-proxy-server/pom.xml b/neutrino-proxy-server/pom.xml index 5f93a05a..6056881b 100644 --- a/neutrino-proxy-server/pom.xml +++ b/neutrino-proxy-server/pom.xml @@ -51,6 +51,10 @@ mysql mysql-connector-java + + org.mariadb.jdbc + mariadb-java-client + com.zaxxer HikariCP diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/ProxyServer.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/ProxyServer.java index f9924e1a..e4fa7703 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/ProxyServer.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/ProxyServer.java @@ -1,15 +1,25 @@ package org.dromara.neutrinoproxy.server; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.dromara.solonplugins.job.annotation.EnableJob; import org.noear.solon.Solon; +import org.noear.solon.SolonApp; import org.noear.solon.annotation.SolonMain; import org.noear.solon.web.cors.CrossFilter; +import org.slf4j.LoggerFactory; + +import java.util.List; /** * * @author: aoshiguchen * @date: 2022/6/16 */ +@Slf4j @EnableJob @SolonMain public class ProxyServer { @@ -18,6 +28,25 @@ public class ProxyServer { Solon.start(ProxyServer.class, args, app -> { // 跨域支持。加-1 优先级更高 app.filter(-1, new CrossFilter().allowedOrigins("*")); + // 设置日志级别 + setLogLevel(app); }); } + + private static void setLogLevel(SolonApp app) { + String loggerLevel = app.cfg().get("neutrino.proxy.logger.level"); + if (StringUtils.isBlank(loggerLevel)) { + return; + } + + try { + Level level = Level.toLevel(loggerLevel); + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + for (Logger logger : loggerContext.getLoggerList()) { + logger.setLevel(level); + } + } catch (Exception e) { + log.error("日志级别设置失败", e); + } + } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DbConfiguration.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DbConfiguration.java index 55f86cca..d30ece9b 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DbConfiguration.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DbConfiguration.java @@ -31,7 +31,6 @@ public class DbConfiguration { dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue()); return dataSource; } else if (DbTypeEnum.MYSQL == dbTypeEnum) { - HikariDataSource dataSource = new HikariDataSource(); String driver = "com.mysql.cj.jdbc.Driver"; try { Class.forName(driver); @@ -39,24 +38,26 @@ public class DbConfiguration { // 对类名的判断,异常则说明不存在: driver = "com.mysql.jdbc.Driver"; } - dataSource.setDriverClassName(driver); - dataSource.setJdbcUrl(dbConfig.getUrl()); - dataSource.setMinimumIdle(5); - dataSource.setMaximumPoolSize(20); - dataSource.setMaxLifetime(60000); -// dataSource.setInitialSize(5); -// dataSource.setMinIdle(5); -// dataSource.setMaxActive(20); -// dataSource.setMaxWait(60000); -// dataSource.setPoolPreparedStatements(true); - dataSource.setUsername(dbConfig.getUsername()); - dataSource.setPassword(dbConfig.getPassword()); - return dataSource; + return newHikariDataSource(dbConfig, driver); + } else if (DbTypeEnum.MARIADB == dbTypeEnum) { + return newHikariDataSource(dbConfig, "org.mariadb.jdbc.Driver"); } return null; } + private HikariDataSource newHikariDataSource(DbConfig dbConfig, String driverClass) { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setDriverClassName(driverClass); + dataSource.setJdbcUrl(dbConfig.getUrl()); + dataSource.setMinimumIdle(5); + dataSource.setMaximumPoolSize(20); + dataSource.setMaxLifetime(60000); + dataSource.setUsername(dbConfig.getUsername()); + dataSource.setPassword(dbConfig.getPassword()); + return dataSource; + } + @Bean public void db1_ext(@Db("db") GlobalConfig globalConfig) { MetaObjectHandler metaObjectHandler = new MetaObjectHandlerImpl(); diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java index 0ec22f42..928db99f 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java @@ -49,6 +49,7 @@ public class ProxyConfig { private Integer httpsProxyPort; private String keyStorePassword; private String jksPath; + private Boolean transferLogEnable; } @Data @@ -60,6 +61,8 @@ public class ProxyConfig { private String keyStorePassword; private String keyManagerPassword; private String jksPath; + private Boolean transferLogEnable; + private Boolean heartbeatLogEnable; } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java index a4c2ca6b..b7537641 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java @@ -1,5 +1,10 @@ package org.dromara.neutrinoproxy.server.base.proxy; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LoggingHandler; import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; import org.dromara.neutrinoproxy.core.ProxyMessage; import org.dromara.neutrinoproxy.core.ProxyMessageHandler; @@ -7,6 +12,9 @@ import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher; import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.nio.NioEventLoopGroup; +import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler; +import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer; +import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler; import org.noear.solon.Solon; import org.noear.solon.annotation.Bean; import org.noear.solon.annotation.Configuration; @@ -43,6 +51,27 @@ public class ProxyConfiguration implements LifecycleBean { return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount()); } + @Bean("tcpServerBootstrap") + public ServerBootstrap tcpServerBootstrap(@Inject("serverBossGroup") NioEventLoopGroup serverBossGroup, + @Inject("serverWorkerGroup") NioEventLoopGroup serverWorkerGroup, + @Inject ProxyConfig proxyConfig + ) { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(serverBossGroup, serverWorkerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class)); + } + ch.pipeline().addFirst(new BytesMetricsHandler()); + ch.pipeline().addLast(new TcpVisitorChannelHandler()); + } + }); + return bootstrap; + } + @Bean("tunnelBossGroup") public NioEventLoopGroup tunnelBossGroup(@Inject ProxyConfig proxyConfig) { return new NioEventLoopGroup(proxyConfig.getTunnel().getBossThreadCount()); diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/rest/interceptor/VisitLogInterceptor.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/rest/interceptor/VisitLogInterceptor.java index 97d46072..181289da 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/rest/interceptor/VisitLogInterceptor.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/rest/interceptor/VisitLogInterceptor.java @@ -31,7 +31,7 @@ public class VisitLogInterceptor implements RouterInterceptor { } finally { Date now = new Date(); long elapsedTime = now.getTime() - startTime.getTime(); - log.info("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n", + log.debug("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n", ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime), JSONObject.toJSONString(ctx.paramMap()), JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)), diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/DbTypeEnum.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/DbTypeEnum.java index f61ccdab..cef27e46 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/DbTypeEnum.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/DbTypeEnum.java @@ -38,7 +38,9 @@ import java.util.stream.Stream; @AllArgsConstructor public enum DbTypeEnum { SQLITE("sqlite"), - MYSQL("mysql"); + MYSQL("mysql"), + MARIADB("mariadb"), + ; private String type; diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java index 25b4677b..9c1fb245 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java @@ -2,6 +2,7 @@ package org.dromara.neutrinoproxy.server.constant; import lombok.AllArgsConstructor; import lombok.Getter; +import org.apache.commons.lang3.StringUtils; import java.util.Map; import java.util.function.Function; @@ -15,14 +16,26 @@ import java.util.stream.Stream; @Getter @AllArgsConstructor public enum NetworkProtocolEnum { - TCP("TCP"), - UDP("UDP"), - HTTP("HTTP"), + TCP("TCP", "TCP"), + UDP("UDP", "UDP"), + HTTP("HTTP", "TCP"), ; private String desc; + private String baseProtocol; private static final Map map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity())); public static NetworkProtocolEnum of(String desc) { + if (StringUtils.isBlank(desc)) { + return null; + } + if (desc.startsWith("HTTP")) { + return NetworkProtocolEnum.HTTP; + } return map.get(desc); } + + public static Boolean isHttp(String desc) { + NetworkProtocolEnum networkProtocolEnum = of(desc); + return NetworkProtocolEnum.HTTP == networkProtocolEnum; + } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java index 773b264e..5a52640c 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java @@ -51,6 +51,7 @@ public class PortMappingController { // 目前仅HTTP支持绑定域名 req.setSubdomain(null); } + req.setProtocal(networkProtocolEnum.getDesc()); return portMappingService.create(req); } @@ -74,6 +75,7 @@ public class PortMappingController { // 目前仅HTTP支持绑定域名 req.setSubdomain(null); } + req.setProtocal(networkProtocolEnum.getDesc()); return portMappingService.update(req); } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/job/DemoJob.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/job/DemoJob.java index 687e623b..4696dca2 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/job/DemoJob.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/job/DemoJob.java @@ -17,6 +17,6 @@ public class DemoJob implements IJobHandler { @Override public void execute(String param) throws Exception { - System.out.println("DemoJob execute param:" + param); + log.debug("DemoJob execute param: {}", param); } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ServerChannelHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyTunnelChannelHandler.java similarity index 68% rename from neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ServerChannelHandler.java rename to neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyTunnelChannelHandler.java index b0b68b00..21c800d5 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ServerChannelHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyTunnelChannelHandler.java @@ -25,6 +25,7 @@ package org.dromara.neutrinoproxy.server.proxy.core; import org.dromara.neutrinoproxy.core.Constants; import org.dromara.neutrinoproxy.core.ProxyMessage; import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; +import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig; import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum; import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum; import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO; @@ -47,15 +48,23 @@ import java.util.Date; * @date: 2022/6/16 */ @Slf4j -public class ServerChannelHandler extends SimpleChannelInboundHandler { +public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler { private static volatile Dispatcher dispatcher; + private static volatile Boolean transferLogEnable = Boolean.FALSE; - public ServerChannelHandler() { + public ProxyTunnelChannelHandler() { dispatcher = Solon.context().getBean(Dispatcher.class); + ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class); + if (null != proxyConfig.getTunnel() && null != proxyConfig.getTunnel().getHeartbeatLogEnable()) { + transferLogEnable = proxyConfig.getTunnel().getHeartbeatLogEnable(); + } } @Override protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception { + if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) { + log.debug("Server CmdChannel recieved proxy message, type is {}", proxyMessage.getType()); + } dispatcher.dispatch(ctx, proxyMessage); } @@ -89,17 +98,24 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler { +public class ProxyTunnelServer implements EventListener { @Inject private ProxyConfig proxyConfig; @Inject("tunnelBossGroup") @@ -112,12 +112,14 @@ public class ProxyServerRunner implements EventListener { } private void proxyServerCommonInitHandler(SocketChannel ch) { -// ch.pipeline().addFirst(new LoggingHandler(ProxyServerRunner.class)); + if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class)); + } ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(), proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(), proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip())); ch.pipeline().addLast(new ProxyMessageEncoder()); ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds())); - ch.pipeline().addLast(new ServerChannelHandler()); + ch.pipeline().addLast(new ProxyTunnelChannelHandler()); } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/enhance/HttpProxy.java similarity index 72% rename from neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java rename to neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/enhance/HttpProxy.java index fbbe4e05..78d2c376 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/enhance/HttpProxy.java @@ -1,4 +1,4 @@ -package org.dromara.neutrinoproxy.server.proxy.core; +package org.dromara.neutrinoproxy.server.proxy.enhance; import cn.hutool.core.util.StrUtil; import io.netty.bootstrap.ServerBootstrap; @@ -6,24 +6,24 @@ import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LoggingHandler; import lombok.extern.slf4j.Slf4j; import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig; +import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler; +import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer; import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Inject; import org.noear.solon.core.event.AppLoadEndEvent; import org.noear.solon.core.event.EventListener; /** + * HTTP代理 * @author: aoshiguchen * @date: 2023/4/2 */ @Slf4j @Component public class HttpProxy implements EventListener { - @Inject("serverBossGroup") - private NioEventLoopGroup serverBossGroup; - @Inject("serverWorkerGroup") - private NioEventLoopGroup serverWorkerGroup; @Inject private ProxyConfig proxyConfig; @Override @@ -38,16 +38,19 @@ public class HttpProxy implements EventListener { private void start() { try { ServerBootstrap bootstrap = new ServerBootstrap(); - bootstrap.group(serverBossGroup, serverWorkerGroup) + bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { + if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class)); + } ch.pipeline().addFirst(new BytesMetricsHandler()); ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName())); } }); bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync(); - log.info("Http代理服务启动成功!"); + log.info("Http代理服务启动成功!port:{}", proxyConfig.getServer().getHttpProxyPort()); } catch (Exception e) { log.error("http proxy start err!", e); } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpVisitorChannelHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/enhance/HttpVisitorChannelHandler.java similarity index 97% rename from neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpVisitorChannelHandler.java rename to neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/enhance/HttpVisitorChannelHandler.java index adf00073..b17ec7bb 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpVisitorChannelHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/enhance/HttpVisitorChannelHandler.java @@ -1,4 +1,4 @@ -package org.dromara.neutrinoproxy.server.proxy.core; +package org.dromara.neutrinoproxy.server.proxy.enhance; import cn.hutool.core.util.StrUtil; import io.netty.buffer.ByteBuf; @@ -68,11 +68,11 @@ public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler { - @Inject("serverBossGroup") - private NioEventLoopGroup serverBossGroup; - @Inject("serverWorkerGroup") - private NioEventLoopGroup serverWorkerGroup; @Inject private ProxyConfig proxyConfig; @Override @@ -46,17 +46,20 @@ public class HttpsProxy implements EventListener { private void start() { try { ServerBootstrap bootstrap = new ServerBootstrap(); - bootstrap.group(serverBossGroup, serverWorkerGroup) + bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { + if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class)); + } ch.pipeline().addLast(createSslHandler()); ch.pipeline().addFirst(new BytesMetricsHandler()); ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName())); } }); bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync(); - log.info("Https代理服务启动成功!"); + log.info("Https代理服务启动成功!port:{}", proxyConfig.getServer().getHttpsProxyPort()); } catch (Exception e) { log.error("https proxy start err!", e); } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java index a43584da..ce3509c8 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageAuthHandler.java @@ -79,9 +79,19 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { String ip = ((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress(); Date now = new Date(); + String info = proxyMessage.getInfo(); + String[] tmp = info.split(","); String licenseKey = proxyMessage.getInfo(); + String clientId = ""; + if (tmp.length == 2) { + licenseKey = tmp[0]; + clientId = tmp[1]; + } + if (StrUtil.isEmpty(licenseKey)) { + log.warn("[客户端连接] license不能为空 info:{} ", info); ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey)); + ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) .setType(ClientConnectTypeEnum.CONNECT.getType()) @@ -94,7 +104,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { } LicenseDO licenseDO = licenseService.findByKey(licenseKey); if (null == licenseDO) { + log.warn("[客户端连接] license不存在 info:{} ", info); ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey)); + ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) .setType(ClientConnectTypeEnum.CONNECT.getType()) @@ -106,7 +118,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { return; } if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) { + log.warn("[客户端连接] 当前license已被禁用 info:{} ", info); ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被禁用!", licenseKey)); + ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) .setLicenseId(licenseDO.getId()) @@ -119,7 +133,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { } UserDO userDO = userService.findById(licenseDO.getUserId()); if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) { + log.warn("[客户端连接] 当前license无效 info:{} ", info); ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license无效!", licenseKey)); + ctx.channel().close(); clientConnectRecordService.add(new ClientConnectRecordDO() .setIp(ip) .setLicenseId(licenseDO.getId()) @@ -132,16 +148,21 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { } Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId()); if (null != cmdChannel) { - ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey)); - clientConnectRecordService.add(new ClientConnectRecordDO() - .setIp(ip) - .setLicenseId(licenseDO.getId()) - .setType(ClientConnectTypeEnum.CONNECT.getType()) - .setMsg(licenseKey) - .setCode(SuccessCodeEnum.FAIL.getCode()) - .setErr("当前license已被另一节点使用!") - .setCreateTime(now)); - return; + String _clientId = ProxyUtil.getClientIdByLicenseId(licenseDO.getId()); + if (!clientId.equals(_clientId)) { + log.warn("[客户端连接] 当前license已被另一节点使用 info:{} _clientId:{}", info, _clientId); + ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey)); + ctx.channel().close(); + clientConnectRecordService.add(new ClientConnectRecordDO() + .setIp(ip) + .setLicenseId(licenseDO.getId()) + .setType(ClientConnectTypeEnum.CONNECT.getType()) + .setMsg(licenseKey) + .setCode(SuccessCodeEnum.FAIL.getCode()) + .setErr("当前license已被另一节点使用!") + .setCreateTime(now)); + return; + } } // 发送认证成功消息 ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "认证成功!", licenseKey)); @@ -150,10 +171,15 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler { .setIp(ip) .setLicenseId(licenseDO.getId()) .setType(ClientConnectTypeEnum.CONNECT.getType()) - .setMsg(licenseKey) + .setMsg(info) .setCode(SuccessCodeEnum.SUCCESS.getCode()) .setCreateTime(now)); + // 设置当前licenseId对应的客户端ID + ProxyUtil.setLicenseIdToClientIdMap(licenseDO.getId(), clientId); + + log.warn("[客户端连接] 认证成功 info:{} ", info); + // 更新license在线状态 licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now); // 初始化VisitorChannel diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/JobLogService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/JobLogService.java index 7b9f321c..b9fd3b44 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/JobLogService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/JobLogService.java @@ -61,7 +61,7 @@ public class JobLogService implements IJobCallback { String msg = ""; if (null == throwable) { msg = "执行成功"; - log.info("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName()); + log.debug("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName()); } else { log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable); msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable); diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/LicenseService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/LicenseService.java index e1a66a03..7b96fda3 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/LicenseService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/LicenseService.java @@ -26,7 +26,6 @@ import org.dromara.neutrinoproxy.server.util.ParamCheckUtil; import ma.glasnost.orika.MapperFacade; import org.apache.ibatis.solon.annotation.Db; import org.dromara.neutrinoproxy.server.controller.res.proxy.*; -import org.jetbrains.annotations.Nullable; import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Init; import org.noear.solon.annotation.Inject; @@ -90,7 +89,6 @@ public class LicenseService implements LifecycleBean { return licenseList; } - @Nullable private List assembleConvertLicenses(List list) { List licenseList = mapperFacade.mapAsList(list, LicenseListRes.class); if (!CollectionUtil.isEmpty(licenseList)) { diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java index 5867ece7..76a5e881 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java @@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.google.common.collect.Sets; +import ma.glasnost.orika.MapperFacade; +import org.apache.ibatis.solon.annotation.Db; import org.dromara.neutrinoproxy.server.base.db.DBInitialize; import org.dromara.neutrinoproxy.server.base.page.PageInfo; import org.dromara.neutrinoproxy.server.base.page.PageQuery; @@ -31,14 +33,10 @@ import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO; import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO; import org.dromara.neutrinoproxy.server.dal.entity.UserDO; import org.dromara.neutrinoproxy.server.util.ParamCheckUtil; -import ma.glasnost.orika.MapperFacade; -import org.apache.ibatis.solon.annotation.Db; -import org.dromara.neutrinoproxy.server.controller.res.proxy.*; import org.dromara.neutrinoproxy.server.util.ProxyUtil; import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Init; import org.noear.solon.annotation.Inject; -import org.noear.solon.core.Lifecycle; import org.noear.solon.core.bean.LifecycleBean; import java.util.*; @@ -51,25 +49,25 @@ import java.util.stream.Collectors; */ @Component public class PortMappingService implements LifecycleBean { - @Inject - private MapperFacade mapperFacade; - @Db - private PortMappingMapper portMappingMapper; - @Db - private LicenseMapper licenseMapper; - @Db - private UserMapper userMapper; - @Db - private PortPoolMapper portPoolMapper; - @Inject - private VisitorChannelService visitorChannelService; + @Inject + private MapperFacade mapperFacade; + @Db + private PortMappingMapper portMappingMapper; + @Db + private LicenseMapper licenseMapper; + @Db + private UserMapper userMapper; + @Db + private PortPoolMapper portPoolMapper; + @Inject + private VisitorChannelService visitorChannelService; - @Inject - private PortPoolService portPoolService; - @Inject - private ProxyConfig proxyConfig; - @Inject - private DBInitialize dbInitialize; + @Inject + private PortPoolService portPoolService; + @Inject + private ProxyConfig proxyConfig; + @Inject + private DBInitialize dbInitialize; public PageInfo page(PageQuery pageQuery, PortMappingListReq req) { Page result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize()); @@ -77,6 +75,13 @@ public class PortMappingService implements LifecycleBean { //描述字段为模糊查询,在应用层处理,否则sqlite不支持 req.setDescription("%" + req.getDescription() + "%"); } + + // 协议名称转换 + if (StringUtils.isNotBlank(req.getProtocal())) { + NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal()); + req.setProtocal(networkProtocolEnum.getDesc()); + } + List list = portMappingMapper.selectPortMappingByCondition(req); List respList = mapperFacade.mapAsList(list, PortMappingListRes.class); if (CollectionUtils.isEmpty(list)) { @@ -108,12 +113,12 @@ public class PortMappingService implements LifecycleBean { if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) { item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName()); } + if (NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal())) { + item.setProtocal("HTTP(S)"); + } }); //sorted [userId asc] [licenseId asc] [createTime asc] - respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId) - .thenComparing(PortMappingListRes::getLicenseId) - .thenComparing(PortMappingListRes::getCreateTime)) - .collect(Collectors.toList()); + respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId).thenComparing(PortMappingListRes::getLicenseId).thenComparing(PortMappingListRes::getCreateTime)).collect(Collectors.toList()); return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize()); } @@ -146,9 +151,7 @@ public class PortMappingService implements LifecycleBean { // 更新VisitorChannel visitorChannelService.addVisitorChannelByPortMapping(portMappingDO); // 更新域名映射 - if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) && - StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && - StrUtil.isNotBlank(portMappingDO.getSubdomain())) { + if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) { ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort()); } return new PortMappingCreateRes(); @@ -185,14 +188,11 @@ public class PortMappingService implements LifecycleBean { // 更新VisitorChannel visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO); // 删除老的域名映射 - if (NetworkProtocolEnum.HTTP.getDesc().equals(oldPortMappingDO.getProtocal()) && - StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) { + if (NetworkProtocolEnum.isHttp(oldPortMappingDO.getProtocal()) && StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) { ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain()); } // 更新域名映射 - if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) && - StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && - StrUtil.isNotBlank(portMappingDO.getSubdomain())) { + if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) { ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort()); } return new PortMappingUpdateRes(); @@ -203,16 +203,7 @@ public class PortMappingService implements LifecycleBean { if (null == portMappingDO) { return null; } - PortMappingDetailRes res = new PortMappingDetailRes() - .setId(portMappingDO.getId()) - .setLicenseId(portMappingDO.getLicenseId()) - .setServerPort(portMappingDO.getServerPort()) - .setClientIp(portMappingDO.getClientIp()) - .setClientPort(portMappingDO.getClientPort()) - .setIsOnline(portMappingDO.getIsOnline()) - .setEnable(portMappingDO.getEnable()) - .setCreateTime(portMappingDO.getCreateTime()) - .setUpdateTime(portMappingDO.getUpdateTime()); + PortMappingDetailRes res = new PortMappingDetailRes().setId(portMappingDO.getId()).setLicenseId(portMappingDO.getLicenseId()).setServerPort(portMappingDO.getServerPort()).setClientIp(portMappingDO.getClientIp()).setClientPort(portMappingDO.getClientPort()).setIsOnline(portMappingDO.getIsOnline()).setEnable(portMappingDO.getEnable()).setCreateTime(portMappingDO.getCreateTime()).setUpdateTime(portMappingDO.getUpdateTime()); LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId()); if (null != license) { @@ -265,8 +256,7 @@ public class PortMappingService implements LifecycleBean { // 更新VisitorChannel visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO); // 更新域名映射 - if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) && - StrUtil.isNotBlank(portMappingDO.getSubdomain())) { + if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) { ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain()); } } @@ -292,10 +282,7 @@ public class PortMappingService implements LifecycleBean { if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) { return; } - List portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper() - .eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()) - .isNotNull(PortMappingDO::getSubdomain) - ); + List portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain)); if (CollectionUtil.isEmpty(portMappingDOList)) { return; } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java index 6eb7c5aa..fe550954 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java @@ -41,10 +41,8 @@ import java.util.stream.Collectors; @Slf4j @Component public class VisitorChannelService { - @Inject("serverBossGroup") - private NioEventLoopGroup serverBossGroup; - @Inject("serverWorkerGroup") - private NioEventLoopGroup serverWorkerGroup; + @Inject("tcpServerBootstrap") + private ServerBootstrap tcpServerBootstrap; @Inject private ProxyMutualService proxyMutualService; @Db @@ -218,20 +216,16 @@ public class VisitorChannelService { if (CollectionUtil.isEmpty(portMappingList)) { return; } - ServerBootstrap bootstrap = new ServerBootstrap(); - bootstrap.group(serverBossGroup, serverWorkerGroup) - .channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) throws Exception { - ch.pipeline().addFirst(new BytesMetricsHandler()); - ch.pipeline().addLast(new TcpVisitorChannelHandler()); - } - }); for (PortMappingDO portMapping : portMappingList) { + if (EnableStatusEnum.DISABLE.getStatus().equals(portMapping.getEnable())) { + // 端口映射被禁用了,忽略 TODO 端口被禁用了也需要处理 + continue; + } + // TODO 此处切入,TCP/UDP代理 try { proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort()); - bootstrap.bind(portMapping.getServerPort()).get(); + tcpServerBootstrap.bind(portMapping.getServerPort()).get(); log.info("绑定用户端口: {}", portMapping.getServerPort()); } catch (Exception ex) { // BindException表示该端口已经绑定过 diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java index ec22943b..637284e0 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java @@ -63,6 +63,10 @@ public class ProxyUtil { * 子域名 - 服务端端口映射 */ private static Map subdomainToServerPort = new HashMap<>(); + /** + * licenseId - 客户端Id映射 + */ + private static Map licenseIdToClientIdMap = new HashMap<>(); /** * 初始化代理信息 @@ -387,4 +391,30 @@ public class ProxyUtil { // ignore } } + + /** + * 设置licenseId - clientId映射 + * @param licenseId + * @param clientId + */ + public static void setLicenseIdToClientIdMap(Integer licenseId, String clientId) { + licenseIdToClientIdMap.put(licenseId, clientId); + } + + /** + * 根据licenseId获取clientId + * @param licenseId + * @return + */ + public static String getClientIdByLicenseId(Integer licenseId) { + return licenseIdToClientIdMap.get(licenseId); + } + + /** + * 根据licenseId删除clientId + * @param licenseId + */ + public static void removeClientIdByLicenseId(Integer licenseId) { + licenseIdToClientIdMap.remove(licenseId); + } } diff --git a/neutrino-proxy-server/src/main/resources/app.yml b/neutrino-proxy-server/src/main/resources/app.yml index 76a68f1d..99082f24 100644 --- a/neutrino-proxy-server/src/main/resources/app.yml +++ b/neutrino-proxy-server/src/main/resources/app.yml @@ -1,5 +1,7 @@ server: + # 服务端web端口,用于支持HTTP接口,管理后台页面访问 port: ${WEB_PORT:8888} + context-path: /neutrino-proxy-server neutrino: proxy: @@ -9,31 +11,53 @@ neutrino: length-field-length: 4 initial-bytes-to-strip: 0 length-adjustment: 0 - read-idle-time: 40 + read-idle-time: 30 write-idle-time: 5 all-idle-time-seconds: 0 + logger: + # 日志级别 + level: ${LOG_LEVEL:info} + # 隧道相关配置-用于维持服务端与客户端的通信 tunnel: + # 线程池相关配置,用于技术调优,可忽略 boss-thread-count: 2 work-thread-count: 10 + # 隧道非SSL端口 port: ${OPEN_PORT:9000} + # 隧道SSL端口 ssl-port: ${SSL_PORT:9002} + # 隧道SSL证书配置 key-store-password: ${STORE_PASS:123456} key-manager-password: ${MGR_PASS:123456} jks-path: ${JKS_PATH:classpath:/test.jks} + # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${TUNNEL_LOG:false} + # 是否开启心跳日志 + heartbeat-log-enable: ${HEARTBEAT_LOG:false} server: + # 线程池相关配置,用于技术调优,可忽略 boss-thread-count: 5 work-thread-count: 20 + # http代理端口,默认80 http-proxy-port: ${HTTP_PROXY_PORT:80} + # https代理端口,默认443 (需要配置域名、证书) https-proxy-port: ${HTTPS_PROXY_PORT:443} # 如果不配置,则不支持域名映射 domain-name: ${DOMAIN_NAME:} + # https证书配置 key-store-password: ${HTTPS_STORE_PASS:} jks-path: ${HTTPS_JKS_PATH:} + # 是否开启代理服务报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${SERVER_LOG:false} data: db: + # 数据库类型,目前支持sqlite、mysql、mariadb type: ${DB_TYPE:sqlite} + # 数据库连接URL url: ${DB_URL:jdbc:sqlite:data.db} + # 数据库用户名 username: ${DB_USER:} + # 数据库密码 password: ${DB_PASSWORD:} #添加MIME印射(如果有需要?) diff --git a/neutrino-proxy-server/src/main/resources/logback.xml b/neutrino-proxy-server/src/main/resources/logback.xml index 380f20ed..005ce00d 100644 --- a/neutrino-proxy-server/src/main/resources/logback.xml +++ b/neutrino-proxy-server/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/init-structure.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/init-structure.sql new file mode 100644 index 00000000..af02f159 --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/init-structure.sql @@ -0,0 +1,207 @@ +#############################系统管理相关表############################# +#用户表 +CREATE TABLE IF NOT EXISTS `user` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) NOT NULL COMMENT '用户名', + `login_name` varchar(50) NOT NULL COMMENT '登录名', + `login_password` varchar(255) NOT NULL COMMENT '登录密码', + `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `I_user_login_name` (`login_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#用户token表 +CREATE TABLE IF NOT EXISTS `user_token` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `token` varchar(50) NOT NULL COMMENT 'token', + `user_id` int NOT NULL COMMENT '用户ID', + `expiration_time` datetime(3) NOT NULL COMMENT '过期时间', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `I_user_token_user_id` (`user_id`), + KEY `I_user_token_token` (`token`), + KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#端口池 +CREATE TABLE IF NOT EXISTS `port_pool` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `group_id` int NOT NULL DEFAULT 1 COMMENT '分组ID', + `port` int NOT NULL COMMENT '端口', + `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `I_port_pool_port` (`port`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#端口分组 +CREATE TABLE IF NOT EXISTS `port_group` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(255) NOT NULL COMMENT '分组名称', + `possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ', + `possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)', + `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; + +#############################代理配置相关表############################# +#license表 +CREATE TABLE IF NOT EXISTS `license` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) NOT NULL COMMENT 'license名称', + `key` varchar(100) NOT NULL COMMENT 'license key', + `user_id` int NOT NULL COMMENT '用户ID', + `is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)', + `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `I_license_key` (`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#端口映射表 +CREATE TABLE IF NOT EXISTS `port_mapping` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `license_id` int NOT NULL COMMENT 'licenseID', + `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议', + `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)', + `server_port` int NOT NULL COMMENT '服务端端口', + `client_ip` varchar(20) NOT NULL COMMENT '客户端IP', + `client_port` int NOT NULL COMMENT '客户端端口', + `is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)', + `description` varchar(100) DEFAULT NULL COMMENT '描述', + `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `I_port_mapping_server_port` (`server_port`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +#############################日志管理相关表############################# +#用户登录记录表 +CREATE TABLE IF NOT EXISTS `user_login_record` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int NOT NULL COMMENT '用户ID', + `ip` varchar(50) NOT NULL COMMENT 'IP', + `token` varchar(100) NOT NULL COMMENT 'token', + `type` int NOT NULL COMMENT '类型(1、登录 2、登出)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +#客户端连接记录表 +CREATE TABLE IF NOT EXISTS `client_connect_record` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `ip` varchar(50) NOT NULL COMMENT 'IP', + `license_id` int NOT NULL COMMENT 'licenseId', + `type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)', + `msg` varchar(512) DEFAULT NULL COMMENT '消息', + `code` int NOT NULL COMMENT '结果 (1、成功 2、失败)', + `err` text DEFAULT NULL COMMENT '异常信息', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +#############################调度管理相关表############################# +#触发器信息表 +CREATE TABLE IF NOT EXISTS `job_info` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `desc` varchar(255) NOT NULL COMMENT '描述', + `handler` varchar(255) NOT NULL COMMENT '处理器', + `cron` varchar(128) NOT NULL COMMENT 'cron', + `param` varchar(512) DEFAULT NULL COMMENT '参数', + `alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮箱', + `alarm_ding` varchar(255) DEFAULT NULL COMMENT '报警钉钉配置', + `enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + `update_time` datetime(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `I_job_info_handler` (`handler`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#触发器日志表 +CREATE TABLE IF NOT EXISTS `job_log` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `job_id` int NOT NULL COMMENT 'JobId', + `handler` varchar(255) NOT NULL COMMENT '处理器', + `param` varchar(512) DEFAULT NULL COMMENT '参数', + `code` int NOT NULL COMMENT '结果(1、成功 2、失败)', + `msg` text COMMENT '消息', + `alarm_status` int NOT NULL COMMENT '报警状态(1、未报警 2、已报警)', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `I_job_log_create_time` (`create_time`) USING BTREE, + KEY `I_job_log_code` (`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#############################报表管理相关表############################# +#流量统计报表-分钟(保留24小时) +CREATE TABLE IF NOT EXISTS `flow_report_minute` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int NOT NULL COMMENT '用户ID', + `license_id` int NOT NULL COMMENT 'licenseId', + `write_bytes` int NOT NULL COMMENT '写入流量', + `read_bytes` int NOT NULL COMMENT '读取流量', + `date` datetime(3) NOT NULL COMMENT '时间', + `date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH:mm', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `I_flow_report_minute_create_time` (`create_time`) USING BTREE, + KEY `I_flow_report_minute_date` (`date`) USING BTREE, + KEY `I_flow_report_minute_user_id` (`user_id`), + KEY `I_flow_report_minute_license_id` (`license_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#流量统计报表-小时(保留60天) +CREATE TABLE IF NOT EXISTS `flow_report_hour` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int NOT NULL COMMENT '用户ID', + `license_id` int NOT NULL COMMENT 'licenseId', + `write_bytes` int NOT NULL COMMENT '写入流量', + `read_bytes` int NOT NULL COMMENT '读取流量', + `date` datetime(3) NOT NULL COMMENT '时间', + `date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE, + KEY `I_flow_report_hour_date` (`date`) USING BTREE, + KEY `I_flow_report_hour_user_id` (`user_id`), + KEY `I_flow_report_hour_license_id` (`license_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#流量统计报表-天(保留1年) +CREATE TABLE IF NOT EXISTS `flow_report_day` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int NOT NULL COMMENT '用户ID', + `license_id` int NOT NULL COMMENT 'licenseId', + `write_bytes` int NOT NULL COMMENT '写入流量', + `read_bytes` int NOT NULL COMMENT '读取流量', + `date` datetime(3) NOT NULL COMMENT '时间', + `date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE, + KEY `I_flow_report_day_date` (`date`) USING BTREE, + KEY `I_flow_report_day_user_id` (`user_id`), + KEY `I_flow_report_day_license_id` (`license_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +#流量统计报表-月(全量保留) +CREATE TABLE IF NOT EXISTS `flow_report_month` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int NOT NULL COMMENT '用户ID', + `license_id` int NOT NULL COMMENT 'licenseId', + `write_bytes` int NOT NULL COMMENT '写入流量', + `read_bytes` int NOT NULL COMMENT '读取流量', + `date` datetime(3) NOT NULL COMMENT '时间', + `date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM', + `create_time` datetime(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE, + KEY `I_flow_report_month_date` (`date`) USING BTREE, + KEY `I_flow_report_month_user_id` (`user_id`), + KEY `I_flow_report_month_license_id` (`license_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/job_info.data.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/job_info.data.sql new file mode 100644 index 00000000..278387ba --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/job_info.data.sql @@ -0,0 +1,13 @@ +#job_qrtz_trigger_info +INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES +(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now()); +INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES +(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now()); +INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES +(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now()); +INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES +(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now()); +INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES +(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now()); +INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES +(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now()); \ No newline at end of file diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/license.data.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/license.data.sql new file mode 100644 index 00000000..f7e239a2 --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/license.data.sql @@ -0,0 +1,3 @@ +#license +INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES +(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now()); diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/port_group.data.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/port_group.data.sql new file mode 100644 index 00000000..65cdaebf --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/port_group.data.sql @@ -0,0 +1,3 @@ +#port_group +INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES +(1, '全局(默认)', 0, -1, 1, now(), now()); diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/port_mapping.data.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/port_mapping.data.sql new file mode 100644 index 00000000..9063ecea --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/port_mapping.data.sql @@ -0,0 +1,7 @@ +#port_mapping +INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES +(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now()); +INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES +(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now()); +INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES +(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now()); diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/port_pool.data.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/port_pool.data.sql new file mode 100644 index 00000000..e4288b13 --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/port_pool.data.sql @@ -0,0 +1,41 @@ +#端口池 +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(1, 1, 9101, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(2, 1, 9102, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(3, 1, 9103, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(4, 1, 9104, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(5, 1, 9105, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(6, 1, 9106, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(7, 1, 9107, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(8, 1, 9108, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(9, 1, 9109, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(10, 1, 9110, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(11, 1, 9111, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(12, 1, 9112, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(13, 1, 9113, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(14, 1, 9114, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(15, 1, 9115, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(16, 1, 9116, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(17, 1, 9117, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(18, 1, 9118, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(19, 1, 9119, 1, now(), now()); +INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES +(20, 1, 9120, 1, now(), now()); diff --git a/neutrino-proxy-server/src/main/resources/sql/mariadb/user.data.sql b/neutrino-proxy-server/src/main/resources/sql/mariadb/user.data.sql new file mode 100644 index 00000000..1be853ba --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mariadb/user.data.sql @@ -0,0 +1,5 @@ +#用户表 6613b92b77056faeb72068f184ed4c4f +INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES +(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now()); +INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES +(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now()); diff --git a/neutrino-proxy-vuepress/docs/.vuepress/public/img/logo/xiaonuo.png b/neutrino-proxy-vuepress/docs/.vuepress/public/img/logo/xiaonuo.png new file mode 100644 index 00000000..b40e5dee Binary files /dev/null and b/neutrino-proxy-vuepress/docs/.vuepress/public/img/logo/xiaonuo.png differ diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md index 7e7ef65d..6f181256 100644 --- a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md @@ -7,7 +7,7 @@ article: false ## 1、 部署服务端 ### 1.1、 Docker一键部署 -> 当前最新版本为1.8.2,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.2` 指定版本安装,推荐使用`latest`直接安装最新版。 +> 当前最新版本为1.8.4,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.4` 指定版本安装,推荐使用`latest`直接安装最新版。 #### 使用默认sqlite数据库 ```shell diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md index 5f4c249b..dafff141 100644 --- a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md @@ -19,6 +19,9 @@ server: neutrino: proxy: + logger: + # 日志级别 + level: ${LOG_LEVEL:info} # 隧道相关配置-用于维持服务端与客户端的通信 tunnel: # 线程池相关配置,用于技术调优,可忽略 @@ -32,7 +35,8 @@ neutrino: key-store-password: ${STORE_PASS:123456} key-manager-password: ${MGR_PASS:123456} jks-path: ${JKS_PATH:classpath:/test.jks} - # 代理服务相关配置 + # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${TUNNEL_LOG:false} server: # 线程池相关配置,用于技术调优,可忽略 boss-thread-count: 5 @@ -46,9 +50,11 @@ neutrino: # https证书配置 key-store-password: ${HTTPS_STORE_PASS:} jks-path: ${HTTPS_JKS_PATH:} + # 是否开启代理服务报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${SERVER_LOG:false} data: db: - # 数据库类型,目前支持sqlite、mysql两种 + # 数据库类型,目前支持sqlite、mysql、mariadb type: ${DB_TYPE:sqlite} # 数据库连接URL url: ${DB_URL:jdbc:sqlite:data.db} diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/07.客户端配置.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/07.客户端配置.md new file mode 100644 index 00000000..c9514f00 --- /dev/null +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/07.客户端配置.md @@ -0,0 +1,38 @@ +--- +title: 客户端配置 +date: 2023-06-09 21:15:35 +permalink: /pages/50ce10/ +--- + +# 以下是最新的客户端配置格式(app.yml) +```yml +neutrino: + proxy: + logger: + # 日志级别 + level: ${LOG_LEVEL:info} + client: + # 线程池相关配置,用于技术调优,可忽略 + thread-count: 50 + # 隧道SSL证书配置 + key-store-password: ${STORE_PASS:123456} + jks-path: ${JKS_PATH:classpath:/test.jks} + # 服务端IP + server-ip: ${SERVER_IP:localhost} + # 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port) + server-port: ${SERVER_PORT:9002} + # 是否启用SSL(注意:该配置必须和server-port对应上) + ssl-enable: ${SSL_ENABLE:true} + # 客户端连接唯一凭证 + license-key: ${LICENSE_KEY:} + # 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成) + client-id: ${CLIENT_ID:} + # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${CLIENT_LOG:false} + # 重连设置 + reconnection: + # 重连间隔(秒) + interval-seconds: 10 + # 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启) + unlimited: false +``` diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/100.常见问题汇总.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/100.常见问题汇总.md new file mode 100644 index 00000000..06062e45 --- /dev/null +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/100.常见问题汇总.md @@ -0,0 +1,32 @@ +--- +title: 常见问题汇总 +date: 2023-07-01 13:35:00 +permalink: /pages/a00001/ +--- + +# 1、客户端启动后连不上服务端,一直在重连 + +## 1.1、启动日志包含`not found license-key config`相关字样 +出现这种情况,说明客户端没获取到配置的license,可按如下检查: +- 客户端是否有配置app.yml,app.yml中是否有配置`license-key` +- 客户端启动参数是否指定app.yml配置,若未指定客户端不会加载任何jar外部的配置文件 +- 客户端启动参数指定的配置文件路径是否正确?如:`java -jar neutrino-proxy-client.jar config=app.yml`,代表`app.yml`文件与jar在同级别目录下,如果不在同级别目录,请使用绝对/相对路径指定 +- 除此之外license还可以用以下方法直接在启动参数中指定: + - `java -jar -DLICENSE_KEY=xxxx -DSERVER_IP=x.x.x.x -DSERVER_PORT=9000 -DSSL_ENABLE=false neutrino-proxy-client.jar` + - `java -jar neutrino-proxy-client.jar serverIp=x.x.x.x serverPort=9000 sslEnable=false licenseKey=xxxx` + +## 1.1、配置文件确定都已经配置,且启动参数正确指定了,但是没有出现`认证成功日志` +- 确认客户端配置的`server-ip`,必须是服务端所在机器的ip,且保证客户端所在机器能正常ping通 +- 确认客户端配置的`server-port`与`ssl-enable`,默认情况应配置为9000、false或9002、true,除非服务端app.yml有改动隧道对外端口 +- 确认服务端对外的9000、9002端口是否已经放开 +- 服务端如果采用docker部署,需要确保容器的9000、9002端口映射到宿主机 + +# 2、关于中微子代理涉及的2类SSL证书 +## 2.1、HTTPS证书 +该证书用于服务端支持HTTPS,若需要在服务端`app.yml`中配置即可 + +## 2.2、隧道SSL证书 +该证书用于对客户端-服务端之间的数据通信进行加密,因此客户端/服务端均需要配置该证书。可参考如下命令生成: +``` +keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks" +``` diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/05.赞赏记录/100.2023年.md b/neutrino-proxy-vuepress/docs/01.使用教程/05.赞赏记录/100.2023年.md new file mode 100644 index 00000000..8176aecf --- /dev/null +++ b/neutrino-proxy-vuepress/docs/01.使用教程/05.赞赏记录/100.2023年.md @@ -0,0 +1,25 @@ +--- +title: 2023年 +date: 2023-06-09 21:33:17 +permalink: /pages/cded59/ +--- + +| 日期 | 渠道 | 金额 |昵称| 备注 | +|:-----------|:---|:---|:-|:-----------------| +| 2023-06-29 |微信红包| 16.8 |迟迟🌱| 加油 | +| 2023-06-27 |微信红包| 30 |Arno| | +| 2023-06-25 |Gitee捐助| 10 |zhujue888| 感谢您的开源项目! | +| 2023-06-16 |微信红包| 50 |小跟班| | +| 2023-06-10 |Gitee捐助| 10 |失败女神| 感谢您的开源项目! | +| 2023-06-09 |Gitee捐助| 50 |Admin| 感谢您的开源项目! | +| 2023-06-09 |微信红包| 50 |李阳| 开源无限好 | +| 2023-06-09 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! | +| 2023-06-07 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! | +| 2023-06-03 |Gitee捐助| 50 |Admin| 感谢您的开源项目! | +| 2023-05-29 |微信红包| 20 |TYY| | +| 2023-05-29 |Gitee捐助| 10 |笑看| 感谢您的开源项目! | +| 2023-05-26 |微信红包| 50 |至少还有满天星光照耀你| | +| 2023-02-23 |Gitee捐助| 10 |Yohanes| 感谢您的开源项目! | +| 2023-02-23 |Gitee捐助| 20 |jam_lee| 感谢您的开源项目!希望能支持域名映射| +| 2023-02-10 |Gitee捐助| 5 |实习两年半| 感谢您的开源项目! | +| 2023-02-02 |Gitee捐助| 10 |阳光很暖| 感谢您的开源项目! | diff --git a/neutrino-proxy-vuepress/docs/index.md b/neutrino-proxy-vuepress/docs/index.md index 341e7b0b..20750280 100644 --- a/neutrino-proxy-vuepress/docs/index.md +++ b/neutrino-proxy-vuepress/docs/index.md @@ -292,9 +292,12 @@ postList: none

🤝 友情开源项目

- + + + +

diff --git a/pom.xml b/pom.xml index 57a840a7..b1ce1798 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.noear solon-parent - 2.2.8 + 2.3.6 @@ -97,6 +97,11 @@ mysql-connector-java 5.1.49 + + org.mariadb.jdbc + mariadb-java-client + 2.7.4 + com.zaxxer HikariCP diff --git a/todolist.MD b/todolist.MD index 07476287..c7d9bbc7 100644 --- a/todolist.MD +++ b/todolist.MD @@ -1,10 +1,21 @@ -# 1.x剩余规划 -- [ ] 增加针对https的支持 +# 1.x规划 +- Bug +- 优化 + - 心跳日志开关 + - 客户端启动无限重连开关 +- UDP支持 +- 官网文档完善 + - 常见问题汇总 + - HTTPS配置说明 +- 协议重构 +- 代码重构 +- [x] 适配mariadb # Bug -- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ServerChannelHandler.channelInactive +- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive - windows环境下直接运行发布版的jar包,日志输出乱码 - 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象 +- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。 # 2.x规划 - 插件开发 @@ -19,6 +30,8 @@ - [ ] 支持调整服务端端口、证书 - [ ] 服务端版本、协议版本、客户端版本管理 - [ ] 服务端日志 + - [ ] 支持持管理后台动态调整日志级别。 + - [ ] 动态调整转发、报文、心跳输出 - client+计划启动 - [ ] 安卓客户端 - [ ] 基于electron-egg的多平台客户端 @@ -26,4 +39,4 @@ # 3.x规划 - [ ] 支持针对用户限速、限流 - [ ] 支持P2P穿透 -- [ ] 支持原生编译 \ No newline at end of file +- [ ] 支持原生编译