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 @@


-# 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、代理示意图

-## 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 @@
-
+
.{{ domainName }}
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
+- [ ] 支持原生编译