diff --git a/.idea/icon.png b/.idea/icon.png deleted file mode 100644 index 4b526ba9..00000000 Binary files a/.idea/icon.png and /dev/null differ diff --git a/neutrino-proxy-client-sdk/Dockerfile b/neutrino-proxy-client-sdk/Dockerfile new file mode 100644 index 00000000..e2a41943 --- /dev/null +++ b/neutrino-proxy-client-sdk/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:21-jdk-oracle +#同步时间 +#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ +# apk update && apk add wget unzip vim && apk add -U tzdata && \ +# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone +# 设置时区为北京时间 +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +RUN mkdir -p /root/neutrino-proxy/config +WORKDIR /root/neutrino-proxy +COPY ./target/neutrino-proxy-client.jar /root/neutrino-proxy/neutrino-proxy-client.jar +COPY ./src/main/resources/app-copy.yml /root/neutrino-proxy/config/app.yml +#VOLUME ["/root/neutrino-proxy"] +ENTRYPOINT ["java","-jar","neutrino-proxy-client.jar","config=./config/app.yml"] + +#docker run -it -d --restart=always --name np_client -e SERVER_IP=127.0.0.1 diff --git a/neutrino-proxy-client-sdk/pom.xml b/neutrino-proxy-client-sdk/pom.xml new file mode 100644 index 00000000..9db95010 --- /dev/null +++ b/neutrino-proxy-client-sdk/pom.xml @@ -0,0 +1,62 @@ + + + + neutrino-proxy + org.dromara.neutrino-proxy + ${revision} + ../pom.xml + + 4.0.0 + jar + + neutrino-proxy-client-sdk + + + + org.dromara.neutrino-proxy + neutrino-proxy-core + ${revision} + + + org.noear + solon-lib + + + com.github.jsqlparser + jsqlparser + 4.5 + compile + + + + + ${project.artifactId} + + + src/main/resources + true + + *.yml + + + + ${project.basedir}/src/main/resources + false + + + app-dev.yml + + + + + + + org.noear + solon-maven-plugin + + + + + diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/ProxyClientSdk.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/ProxyClientSdk.java new file mode 100644 index 00000000..c0570f13 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/ProxyClientSdk.java @@ -0,0 +1,48 @@ +package org.dromara.neutrinoproxy.client.sdk; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.noear.solon.Solon; +import org.noear.solon.Utils; +import org.noear.solon.annotation.SolonMain; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +@SolonMain +public class ProxyClientSdk { + + public static void main(String[] args) { + Solon.start(ProxyClientSdk.class, args, app -> { + String loglevel = System.getenv("LOG_LEVEL"); + if (Utils.isNotEmpty(loglevel)) { + app.cfg().put("solon.logging.logger.root.level", loglevel); + } + + setAlias("neutrino.proxy.tunnel.server-ip", "serverIp"); + setAlias("neutrino.proxy.tunnel.server-port", "serverPort"); + setAlias("neutrino.proxy.tunnel.ssl-enable", "sslEnable"); + setAlias("neutrino.proxy.tunnel.jks-path", "jksPath"); + setAlias("neutrino.proxy.tunnel.key-store-password", "keyStorePassword"); + setAlias("neutrino.proxy.tunnel.license-key", "licenseKey"); + + log.info("NeutrinoProxy Client :{}", app.cfg().get("solon.app.version")); + }); + } + + /** + * 别名处理,支持较短的启动参数名 + * @param key + * @param alias + */ + private static void setAlias(String key, String alias) { + String val = Solon.cfg().argx().get(alias); + if (StrUtil.isNotBlank(val)) { + Solon.cfg().put(key, val); + } + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/IBeanHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/IBeanHandler.java new file mode 100644 index 00000000..266d5b3d --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/IBeanHandler.java @@ -0,0 +1,8 @@ +package org.dromara.neutrinoproxy.client.sdk.config; + +import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; + +public interface IBeanHandler { + Dispatcher getDispatcher(); + ProxyConfig getProxyConfig(); +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/IProxyConfiguration.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/IProxyConfiguration.java new file mode 100644 index 00000000..abe2a641 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/IProxyConfiguration.java @@ -0,0 +1,187 @@ +package org.dromara.neutrinoproxy.client.sdk.config; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.timeout.IdleStateHandler; +import org.dromara.neutrinoproxy.client.sdk.core.*; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.ProxyMessageDecoder; +import org.dromara.neutrinoproxy.core.ProxyMessageEncoder; +import org.dromara.neutrinoproxy.core.aot.NeutrinoCoreRuntimeNativeRegistrar; + +import java.net.InetSocketAddress; + +/** + * 代理配置 + * @author: aoshiguchen + * @date: 2022/10/8 + */ + +public abstract class IProxyConfiguration { + + public abstract IBeanHandler getBeanHandler(); + + public NioEventLoopGroup tunnelWorkGroup(ProxyConfig proxyConfig) { + return new NioEventLoopGroup(proxyConfig.getTunnel().getThreadCount()); + } + + public NioEventLoopGroup tcpRealServerWorkGroup(ProxyConfig proxyConfig) { + // 暂时先公用此配置 + return new NioEventLoopGroup(proxyConfig.getTunnel().getThreadCount()); + } + + public NioEventLoopGroup udpServerGroup(ProxyConfig proxyConfig) { + // 暂时先公用此配置 + return new NioEventLoopGroup(proxyConfig.getClient().getUdp().getBossThreadCount()); + } + + public NioEventLoopGroup udpWorkGroup(ProxyConfig proxyConfig) { + // 暂时先公用此配置 + return new NioEventLoopGroup(proxyConfig.getClient().getUdp().getWorkThreadCount()); + } + + public Bootstrap cmdTunnelBootstrap(ProxyConfig proxyConfig, + NioEventLoopGroup tunnelWorkGroup) { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(tunnelWorkGroup); + bootstrap.channel(NioSocketChannel.class); +// bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); +// bootstrap.option(ChannelOption.SO_KEEPALIVE, true); +// /** +// * TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。 +// * Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。 +// */ +// bootstrap.option(ChannelOption.TCP_NODELAY, true); + bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort())); + + bootstrap.handler(new ChannelInitializer() { + + @Override + public void initChannel(SocketChannel ch) throws Exception { + if (proxyConfig.getTunnel().getSslEnable()) { + ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig)); + } + if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(CmdChannelHandler.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(getBeanHandler())); + } + }); + return bootstrap; + } + + public Bootstrap tcpProxyTunnelBootstrap(ProxyConfig proxyConfig, + NioEventLoopGroup tunnelWorkGroup) { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(tunnelWorkGroup); + bootstrap.channel(NioSocketChannel.class); + bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort())); + bootstrap.handler(new ChannelInitializer() { + + @Override + public void initChannel(SocketChannel ch) throws Exception { + if (proxyConfig.getTunnel().getSslEnable()) { + ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig)); + } + if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(TcpProxyChannelHandler.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 TcpProxyChannelHandler(getBeanHandler())); + } + }); + return bootstrap; + } + public Bootstrap udpProxyTunnelBootstrap(ProxyConfig proxyConfig, + NioEventLoopGroup tunnelWorkGroup) { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(tunnelWorkGroup); + bootstrap.channel(NioSocketChannel.class); + bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort())); + bootstrap.handler(new ChannelInitializer() { + + @Override + public void initChannel(SocketChannel ch) throws Exception { + if (proxyConfig.getTunnel().getSslEnable()) { + ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig)); + } + if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(TcpProxyChannelHandler.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 UdpProxyChannelHandler(getBeanHandler())); + } + }); + return bootstrap; + } + + public Bootstrap realServerBootstrap(ProxyConfig proxyConfig, + NioEventLoopGroup tcpRealServerWorkGroup + ) { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(tcpRealServerWorkGroup); + bootstrap.channel(NioSocketChannel.class); + bootstrap.handler(new ChannelInitializer() { + + @Override + public void initChannel(SocketChannel ch) throws Exception { + if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(RealServerChannelHandler.class)); + } + ch.pipeline().addLast(new RealServerChannelHandler()); + } + }); + return bootstrap; + } + + public Bootstrap udpServerBootstrap(ProxyConfig proxyConfig, + NioEventLoopGroup udpServerGroup, + NioEventLoopGroup udpWorkGroup) { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(udpServerGroup) + // 主线程处理 + .channel(NioDatagramChannel.class) + // 广播 + .option(ChannelOption.SO_BROADCAST, true) + // 设置读缓冲区为2M + .option(ChannelOption.SO_RCVBUF, 2048 * 1024) + // 设置写缓冲区为1M + .option(ChannelOption.SO_SNDBUF, 1024 * 1024) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(NioDatagramChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + if (null != proxyConfig.getClient().getUdp().getTransferLogEnable() && proxyConfig.getClient().getUdp().getTransferLogEnable()) { + ch.pipeline().addFirst(new LoggingHandler(UdpRealServerHandler.class)); + } + pipeline.addLast(udpWorkGroup, new UdpRealServerHandler()); + } + }); + return bootstrap; + } + + public NeutrinoCoreRuntimeNativeRegistrar neutrinoCoreRuntimeNativeRegistrar() { + return new NeutrinoCoreRuntimeNativeRegistrar(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/NeutrinoClientRuntimeNativeRegistrar.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/NeutrinoClientRuntimeNativeRegistrar.java new file mode 100644 index 00000000..5b060964 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/NeutrinoClientRuntimeNativeRegistrar.java @@ -0,0 +1,26 @@ +package org.dromara.neutrinoproxy.client.sdk.config; + +import org.noear.solon.annotation.Component; +import org.noear.solon.aot.RuntimeNativeMetadata; +import org.noear.solon.aot.RuntimeNativeRegistrar; +import org.noear.solon.aot.hint.MemberCategory; +import org.noear.solon.core.AppContext; + +/** + * @author songyinyin + * @since 2023/10/21 22:50 + */ +@Component +public class NeutrinoClientRuntimeNativeRegistrar implements RuntimeNativeRegistrar { + @Override + public void register(AppContext context, RuntimeNativeMetadata metadata) { + metadata.registerResourceInclude("test.jks"); + + metadata.registerReflection(ProxyConfig.Protocol.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + metadata.registerReflection(ProxyConfig.Client.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + metadata.registerReflection(ProxyConfig.Tunnel.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + metadata.registerReflection(ProxyConfig.Tcp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + metadata.registerReflection(ProxyConfig.Udp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + metadata.registerReflection(ProxyConfig.Reconnection.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/ProxyConfig.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/ProxyConfig.java new file mode 100644 index 00000000..ba740bef --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/config/ProxyConfig.java @@ -0,0 +1,74 @@ +package org.dromara.neutrinoproxy.client.sdk.config; + +import lombok.Data; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Inject; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Data +@Component +public class ProxyConfig { + @Inject("${neutrino.proxy.protocol}") + private Protocol protocol; + @Inject("${neutrino.proxy.tunnel}") + private Tunnel tunnel; + @Inject("${neutrino.proxy.client}") + private Client client; + + @Data + public static class Protocol { + private Integer maxFrameLength; + private Integer lengthFieldOffset; + private Integer lengthFieldLength; + private Integer initialBytesToStrip; + private Integer lengthAdjustment; + private Integer readIdleTime; + private Integer writeIdleTime; + private Integer allIdleTimeSeconds; + } + + @Data + public static class Tunnel { + private String keyStorePassword; + private String jksPath; + private String serverIp; + private Integer serverPort; + private Boolean sslEnable; + 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 Client { +// private Tcp tcp; + private Udp udp; + } + + @Data + public static class Reconnection { + private Integer intervalSeconds; + private Boolean unlimited; + } + + @Data + public static class Tcp { + + } + + @Data + public static class Udp { + private Integer bossThreadCount; + private Integer workThreadCount; + private String puppetPortRange; + private Boolean transferLogEnable; + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/constant/Constants.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/constant/Constants.java new file mode 100644 index 00000000..02dd5fe8 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/constant/Constants.java @@ -0,0 +1,13 @@ +package org.dromara.neutrinoproxy.client.sdk.constant; + +import io.netty.util.AttributeKey; +import org.dromara.neutrinoproxy.client.sdk.util.UdpChannelBindInfo; + +/** + * @author: aoshiguchen + * @date: 2023/9/21 + */ +public interface Constants { + AttributeKey UDP_CHANNEL_BIND_KEY = AttributeKey.newInstance("udpChannelBindKey"); + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/CmdChannelHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/CmdChannelHandler.java new file mode 100644 index 00000000..f4bb1c67 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/CmdChannelHandler.java @@ -0,0 +1,87 @@ +package org.dromara.neutrinoproxy.client.sdk.core; + +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.sdk.config.IBeanHandler; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyMessage; + +/** + * 处理与服务端之间的数据传输 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +public class CmdChannelHandler extends SimpleChannelInboundHandler{ + private static volatile Boolean transferLogEnable = Boolean.FALSE; + private IBeanHandler beanHandler; + + public CmdChannelHandler(IBeanHandler beanHandler) { + this.beanHandler = beanHandler; + ProxyConfig proxyConfig = beanHandler.getProxyConfig(); + if (null != proxyConfig.getClient() && 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("[CMD Channel]Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType()); + } + beanHandler.getDispatcher().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("[CMD Channel]Client CmdChannel disconnect"); + ProxyUtil.setCmdChannel(null); + ProxyUtil.clearRealServerChannels(); + + super.channelInactive(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("[CMD Channel]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("[CMD Channel] Read timeout disconnect"); + ctx.channel().close(); + break; + case WRITER_IDLE: + ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage()); + break; + case ALL_IDLE: + log.error("[CMD Channel] ReadWrite timeout disconnect"); + ctx.close(); + break; + } + } + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/CustomThreadFactory.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/CustomThreadFactory.java new file mode 100644 index 00000000..1bddce35 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/CustomThreadFactory.java @@ -0,0 +1,55 @@ +/** + * 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.sdk.core; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * + * @author: aoshiguchen + * @date: 2022/9/4 + */ +public class CustomThreadFactory implements ThreadFactory { + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public CustomThreadFactory(String prefix) { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + namePrefix = prefix + "-thread-"; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + if (t.isDaemon()) { + t.setDaemon(false); + } + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/IAbProxyClientService.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/IAbProxyClientService.java new file mode 100644 index 00000000..0af1ef55 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/IAbProxyClientService.java @@ -0,0 +1,127 @@ +package org.dromara.neutrinoproxy.client.sdk.core; + +import cn.hutool.core.util.StrUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.client.sdk.util.UdpServerUtil; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.noear.solon.Solon; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * 代理客户端服务 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +public class IAbProxyClientService { + + public ProxyConfig proxyConfig; + + public Bootstrap cmdTunnelBootstrap; + + public Bootstrap udpServerBootstrap; + + private volatile Channel channel; + /** + * 重连次数 + */ + private volatile int reconnectCount = 0; + + /** + * 重连服务执行器 + */ + public static final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("ClientReconnect")); + + public void init_i(){ + reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getTunnel().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS); + try { + this.start(); + UdpServerUtil.initCache(proxyConfig, udpServerBootstrap); + } catch (Exception e) { + // 启动连不上也做一下重连,因此先catch异常 + log.error("[CmdChannel] start error", e); + } + } + + public void start() { + if (StrUtil.isEmpty(proxyConfig.getTunnel().getServerIp())) { + log.error("not found server-ip config."); + Solon.stop(); + return; + } + if (null == proxyConfig.getTunnel().getServerPort()) { + log.error("not found server-port config."); + Solon.stop(); + return; + } + if (null != proxyConfig.getTunnel().getSslEnable() && proxyConfig.getTunnel().getSslEnable() + && StrUtil.isEmpty(proxyConfig.getTunnel().getJksPath())) { + log.error("not found jks-path config."); + Solon.stop(); + return; + } + if (StrUtil.isEmpty(proxyConfig.getTunnel().getLicenseKey())) { + log.error("not found license-key config."); + Solon.stop(); + return; + } + if (null == channel || !channel.isActive()) { + try { + connectProxyServer(); + } catch (Exception e) { + log.error("client start error", e); + } + } else { + channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getTunnel().getLicenseKey(), ProxyUtil.getClientId())); + } + } + + /** + * 连接代理服务器 + */ + private void connectProxyServer() throws InterruptedException { + cmdTunnelBootstrap.connect() + .addListener(new ChannelFutureListener() { + + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + channel = future.channel(); + // 连接成功,向服务器发送客户端认证信息(licenseKey) + ProxyUtil.setCmdChannel(future.channel()); + future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getTunnel().getLicenseKey(), ProxyUtil.getClientId())); + log.info("[CmdChannel] connect proxy server success. channelId:{}", future.channel().id().asLongText()); + +// reconnectServiceEnable = true; + reconnectCount = 0; + } else { + log.info("[CmdChannel] connect proxy server failed!"); + } + } + }).sync(); + } + + protected synchronized void reconnect() { + if (null != channel) { + if (channel.isActive()) { + return; + } + channel.close(); + } + + log.info("[CmdChannel] client reconnect seq:{}", ++reconnectCount); + try { + connectProxyServer(); + } catch (Exception e) { + log.error("[CmdChannel] reconnect error", e); + } + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/ProxyChannelBorrowListener.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/ProxyChannelBorrowListener.java new file mode 100644 index 00000000..1dce20d2 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/ProxyChannelBorrowListener.java @@ -0,0 +1,38 @@ +/** + * 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.sdk.core; + +import io.netty.channel.Channel; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/16 + */ +public interface ProxyChannelBorrowListener { + + void success(Channel channel); + + void error(Throwable cause); + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/ProxyClientService.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/ProxyClientService.java new file mode 100644 index 00000000..8922a8bc --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/ProxyClientService.java @@ -0,0 +1,31 @@ +package org.dromara.neutrinoproxy.client.sdk.core; + +import io.netty.bootstrap.Bootstrap; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Init; +import org.noear.solon.annotation.Inject; + +/** + * 代理客户端服务 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +@Component +public class ProxyClientService extends IAbProxyClientService{ + @Inject + private ProxyConfig proxyConfig; + @Inject("cmdTunnelBootstrap") + private Bootstrap cmdTunnelBootstrap; + @Inject("udpServerBootstrap") + private Bootstrap udpServerBootstrap; + @Init + public void init() { + super.proxyConfig=proxyConfig; + super.cmdTunnelBootstrap=cmdTunnelBootstrap; + super.udpServerBootstrap=udpServerBootstrap; + init_i(); + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/RealServerChannelHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/RealServerChannelHandler.java new file mode 100644 index 00000000..ea0cb3d5 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/RealServerChannelHandler.java @@ -0,0 +1,103 @@ +/** + * 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.sdk.core; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * 处理与被代理客户端的数据传输 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +public class RealServerChannelHandler extends SimpleChannelInboundHandler { + + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + Channel realServerChannel = ctx.channel(); + Channel proxyChannel = realServerChannel.attr(Constants.NEXT_CHANNEL).get(); + if (null == proxyChannel) { + // 代理客户端连接断开 + ctx.channel().close(); + } else { + + if (proxyChannel.isWritable()) { + if (!realServerChannel.config().isAutoRead()) { + realServerChannel.config().setAutoRead(true); + } + } else { + if (realServerChannel.config().isAutoRead()) { + realServerChannel.config().setAutoRead(false); + } + } + + byte[] bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + String visitorId = ProxyUtil.getVisitorIdByRealServerChannel(realServerChannel); + proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes)); + } + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + Channel realServerChannel = ctx.channel(); + String visitorId = ProxyUtil.getVisitorIdByRealServerChannel(realServerChannel); + ProxyUtil.removeRealServerChannel(visitorId); + Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get(); + if (channel != null) { + channel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId)); + } + + super.channelInactive(ctx); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + Channel realServerChannel = ctx.channel(); + Channel proxyChannel = realServerChannel.attr(Constants.NEXT_CHANNEL).get(); + if (proxyChannel != null) { + proxyChannel.config().setOption(ChannelOption.AUTO_READ, realServerChannel.isWritable()); + } + + super.channelWritabilityChanged(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("Client ProxyChannel Error", cause); + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/TcpProxyChannelHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/TcpProxyChannelHandler.java new file mode 100644 index 00000000..59161321 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/TcpProxyChannelHandler.java @@ -0,0 +1,87 @@ +package org.dromara.neutrinoproxy.client.sdk.core; + +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.sdk.config.IBeanHandler; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.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; + +/** + * 处理与服务端之间的数据传输 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +public class TcpProxyChannelHandler extends SimpleChannelInboundHandler { + + private IBeanHandler beanHandler; + public TcpProxyChannelHandler(IBeanHandler beanHandler) { + this.beanHandler = beanHandler; + } + @Override + protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception { + if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) { + log.debug("[TCP Proxy Channel]Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType()); + } + beanHandler.getDispatcher().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 { + // 数据传输连接 + Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get(); + if (realServerChannel != null && realServerChannel.isActive()) { + realServerChannel.close(); + } + + ProxyUtil.removeTcpProxyChanel(ctx.channel()); + super.channelInactive(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("[TCP Proxy Channel]Client ProxyChannel 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: + if (ctx.channel().isWritable()) { + // 读超时,断开连接 + log.info("[TCP Proxy Channel]Read timeout"); + ctx.channel().close(); + } + break; + case WRITER_IDLE: + ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage()); + break; + case ALL_IDLE: +// log.debug("[TCP Proxy Channel]ReadWrite timeout"); +// ctx.close(); + break; + } + } + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/UdpProxyChannelHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/UdpProxyChannelHandler.java new file mode 100644 index 00000000..40dec1b6 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/UdpProxyChannelHandler.java @@ -0,0 +1,84 @@ +package org.dromara.neutrinoproxy.client.sdk.core; + +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.sdk.config.IBeanHandler; +import org.dromara.neutrinoproxy.client.sdk.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; + +/** + * 处理与服务端之间的数据传输 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +public class UdpProxyChannelHandler extends SimpleChannelInboundHandler { + + private IBeanHandler beanHandler; + public UdpProxyChannelHandler(IBeanHandler beanHandler) { + this.beanHandler = beanHandler; + } + @Override + protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception { + if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) { + log.debug("[UDP Proxy Channel]Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType()); + } + beanHandler.getDispatcher().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 { + // 数据传输连接 + Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get(); + if (realServerChannel != null && realServerChannel.isActive()) { + realServerChannel.close(); + } + + ProxyUtil.removeTcpProxyChanel(ctx.channel()); + super.channelInactive(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("[UDP Proxy Channel]Client ProxyChannel 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.info("[UDP Proxy Channel]Read timeout"); + ctx.channel().close(); + break; + case WRITER_IDLE: + ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage()); + break; + case ALL_IDLE: + log.debug("[UDP Proxy Channel]ReadWrite timeout"); + ctx.close(); + break; + } + } + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/UdpRealServerHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/UdpRealServerHandler.java new file mode 100644 index 00000000..758a5618 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/core/UdpRealServerHandler.java @@ -0,0 +1,41 @@ +package org.dromara.neutrinoproxy.client.sdk.core; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.constant.Constants; +import org.dromara.neutrinoproxy.client.sdk.util.UdpChannelBindInfo; +import org.dromara.neutrinoproxy.core.ProxyMessage; + +import java.net.InetSocketAddress; + +/** + * @author: aoshiguchen + * @date: 2023/9/21 + */ +@Slf4j +public class UdpRealServerHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception { + log.debug("chid---<:{} port:{}", ctx.channel().id().asLongText(), ((InetSocketAddress)ctx.channel().localAddress()).getPort()); + UdpChannelBindInfo udpChannelBindInfo = ctx.channel().attr(Constants.UDP_CHANNEL_BIND_KEY).get(); + if (null != udpChannelBindInfo) { + byte[] bytes = new byte[datagramPacket.content().readableBytes()]; + datagramPacket.content().readBytes(bytes); + + udpChannelBindInfo.getTunnelChannel().writeAndFlush(ProxyMessage.buildUdpTransferMessage(new ProxyMessage.UdpBaseInfo() + .setVisitorId(udpChannelBindInfo.getVisitorId()) + .setVisitorIp(udpChannelBindInfo.getVisitorIp()) + .setVisitorPort(udpChannelBindInfo.getVisitorPort()) + .setServerPort(udpChannelBindInfo.getServerPort()) + .setTargetIp(udpChannelBindInfo.getTargetIp()) + .setTargetPort(udpChannelBindInfo.getTargetPort())) + .setData(bytes) + ); + + udpChannelBindInfo.getLockChannel().setResponseCount(udpChannelBindInfo.getLockChannel().getResponseCount() + 1); + } + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageAuthHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageAuthHandler.java new file mode 100644 index 00000000..5ae9219e --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageAuthHandler.java @@ -0,0 +1,46 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ExceptionEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.noear.snack.ONode; +import org.noear.solon.Solon; + +/** + * 认证信息处理器 + * @author: aoshiguchen + * @date: 2022/9/4 + */ +@Slf4j +@Match(type = Constants.ProxyDataTypeName.AUTH) +public class ProxyMessageAuthHandler implements ProxyMessageHandler { + private ProxyConfig proxyConfig; + + public ProxyMessageAuthHandler(ProxyConfig proxyConfig){ + this.proxyConfig=proxyConfig; + } + @Override + public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) { + String info = proxyMessage.getInfo(); + ONode load = ONode.load(info); + Integer code = load.get("code").getInt(); + log.info("Auth result:{}", info); + if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) { + // 客户端认证失败,直接停止服务 + log.info("client auth failed , client stop."); + context.channel().close(); + if (!proxyConfig.getTunnel().getReconnection().getUnlimited()) { + Solon.stop(); + } + } else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) || + ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code) + ){ + context.channel().close(); + } + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageConnectHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageConnectHandler.java new file mode 100644 index 00000000..804c2cc2 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageConnectHandler.java @@ -0,0 +1,91 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.core.ProxyChannelBorrowListener; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; + +/** + * 连接信息处理器 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +@Match(type = Constants.ProxyDataTypeName.CONNECT) +public class ProxyMessageConnectHandler implements ProxyMessageHandler { + + private Bootstrap tcpProxyTunnelBootstrap; + private Bootstrap realServerBootstrap; + private ProxyConfig proxyConfig; + + public ProxyMessageConnectHandler(Bootstrap tcpProxyTunnelBootstrap,Bootstrap realServerBootstrap,ProxyConfig proxyConfig){ + this.proxyConfig=proxyConfig; + this.realServerBootstrap=realServerBootstrap; + this.tcpProxyTunnelBootstrap=tcpProxyTunnelBootstrap; + } + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + final Channel cmdChannel = ctx.channel(); + final String visitorId = proxyMessage.getInfo(); + String[] serverInfo = new String(proxyMessage.getData()).split(":"); + String ip = serverInfo[0]; + int port = Integer.parseInt(serverInfo[1]); + // 连接真实的、被代理的服务 + realServerBootstrap.connect(ip, port).addListener(new ChannelFutureListener() { + + @Override + public void operationComplete(ChannelFuture future) throws Exception { + + // 连接后端服务器成功 + if (future.isSuccess()) { + final Channel realServerChannel = future.channel(); + + realServerChannel.config().setOption(ChannelOption.AUTO_READ, false); + + // 获取连接 + ProxyUtil.borrowTcpProxyChanel(tcpProxyTunnelBootstrap, new ProxyChannelBorrowListener() { + + @Override + public void success(Channel channel) { + // 连接绑定 + channel.attr(Constants.NEXT_CHANNEL).set(realServerChannel); + realServerChannel.attr(Constants.NEXT_CHANNEL).set(channel); + + // 远程绑定 + channel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId + "@" + proxyConfig.getTunnel().getLicenseKey())); + + realServerChannel.config().setOption(ChannelOption.AUTO_READ, true); + ProxyUtil.addRealServerChannel(visitorId, realServerChannel); + ProxyUtil.setRealServerChannelVisitorId(realServerChannel, visitorId); + } + + @Override + public void error(Throwable cause) { + ProxyMessage proxyMessage = new ProxyMessage(); + proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT); + proxyMessage.setInfo(visitorId); + cmdChannel.writeAndFlush(proxyMessage); + } + }); + + } else { + cmdChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId)); + } + } + }); + } + + @Override + public String name() { + return ProxyDataTypeEnum.CONNECT.getDesc(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageDisconnectHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageDisconnectHandler.java new file mode 100644 index 00000000..1534aae1 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageDisconnectHandler.java @@ -0,0 +1,40 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; + +/** + * 断开连接信息处理器 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +@Match(type = Constants.ProxyDataTypeName.DISCONNECT) +public class ProxyMessageDisconnectHandler implements ProxyMessageHandler { + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get(); + if (null != realServerChannel) { + ctx.channel().attr(Constants.NEXT_CHANNEL).remove(); + ProxyUtil.returnTcpProxyChanel(ctx.channel()); + realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); + } + ctx.close(); + } + + @Override + public String name() { + return ProxyDataTypeEnum.DISCONNECT.getDesc(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageErrorHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageErrorHandler.java new file mode 100644 index 00000000..66ce7fea --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageErrorHandler.java @@ -0,0 +1,33 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.core.*; +import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.noear.snack.ONode; + +/** + * 异常信息处理器 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Slf4j +@Match(type = Constants.ProxyDataTypeName.ERROR) +public class ProxyMessageErrorHandler implements ProxyMessageHandler { + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + log.info("error: {}", proxyMessage.getInfo()); + ONode load = ONode.load(proxyMessage.getInfo()); + Integer code = load.get("code").getInt(); + if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) { + System.exit(0); + } + } + + @Override + public String name() { + return ProxyDataTypeEnum.DISCONNECT.getDesc(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageTransferHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageTransferHandler.java new file mode 100644 index 00000000..1d2199aa --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/ProxyMessageTransferHandler.java @@ -0,0 +1,41 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; + +/** + * 传输信息处理器 + * @author: aoshiguchen + * @date: 2022/6/16 + */ +@Match(type = Constants.ProxyDataTypeName.TRANSFER) +@Slf4j +public class ProxyMessageTransferHandler implements ProxyMessageHandler { + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get(); + if (realServerChannel != null) { + + // 自己可写,则设置来源可读。自己不可写,则设置来源不可读 + ctx.channel().config().setAutoRead(realServerChannel.isWritable()); + + ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length); + buf.writeBytes(proxyMessage.getData()); + realServerChannel.writeAndFlush(buf); + } + } + + @Override + public String name() { + return ProxyDataTypeEnum.TRANSFER.getDesc(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/UdpProxyMessageConnectHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/UdpProxyMessageConnectHandler.java new file mode 100644 index 00000000..81dc9651 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/UdpProxyMessageConnectHandler.java @@ -0,0 +1,66 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.core.ProxyChannelBorrowListener; +import org.dromara.neutrinoproxy.client.sdk.util.ProxyUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.noear.snack.ONode; + +/** + * @author: aoshiguchen + * @date: 2023/9/19 + */ +@Slf4j +@Match(type = Constants.ProxyDataTypeName.UDP_CONNECT) +public class UdpProxyMessageConnectHandler implements ProxyMessageHandler { + + private ProxyConfig proxyConfig; + private Bootstrap udpProxyTunnelBootstrap; + + public UdpProxyMessageConnectHandler(ProxyConfig proxyConfig,Bootstrap udpProxyTunnelBootstrap){ + this.proxyConfig=proxyConfig; + this.udpProxyTunnelBootstrap=udpProxyTunnelBootstrap; + } + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + final Channel cmdChannel = ctx.channel(); + final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class); + log.info("[UDP connect]info:{}", proxyMessage.getInfo()); + + // 获取连接 + ProxyUtil.borrowUdpProxyChanel(udpProxyTunnelBootstrap, new ProxyChannelBorrowListener() { + + @Override + public void success(Channel channel) { + channel.writeAndFlush(ProxyMessage.buildUdpConnectMessage(new ProxyMessage.UdpBaseInfo() + .setVisitorId(udpBaseInfo.getVisitorId()) + .setServerPort(udpBaseInfo.getServerPort()) + .setTargetIp(udpBaseInfo.getTargetIp()) + .setTargetPort(udpBaseInfo.getTargetPort()) + ).setData(proxyConfig.getTunnel().getLicenseKey().getBytes())); + } + + @Override + public void error(Throwable cause) { + cmdChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(udpBaseInfo.toJsonString())); + } + }); + + + } + + @Override + public String name() { + return ProxyDataTypeEnum.UDP_CONNECT.getDesc(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/UdpProxyMessageTransferHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/UdpProxyMessageTransferHandler.java new file mode 100644 index 00000000..2e97adfa --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/handler/UdpProxyMessageTransferHandler.java @@ -0,0 +1,47 @@ +package org.dromara.neutrinoproxy.client.sdk.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.client.sdk.util.UdpServerUtil; +import org.dromara.neutrinoproxy.core.Constants; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.dispatcher.Match; +import org.noear.snack.ONode; + +import java.net.InetSocketAddress; + +/** + * @author: aoshiguchen + * @date: 2023/9/20 + */ +@Slf4j +@Match(type = Constants.ProxyDataTypeName.UDP_TRANSFER) +public class UdpProxyMessageTransferHandler implements ProxyMessageHandler { + + @Override + public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { + final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class); + log.debug("[UDP transfer]info:{} data:{}", proxyMessage.getInfo(), new String(proxyMessage.getData())); + Channel channel = UdpServerUtil.takeChannel(udpBaseInfo, ctx.channel()); + if (null == channel) { + log.error("[UDP transfer] take udp channel failed."); + return; + } + log.debug("chid--->:{} port:{}", ctx.channel().id().asLongText(), ((InetSocketAddress)channel.localAddress()).getPort()); + InetSocketAddress address = new InetSocketAddress(udpBaseInfo.getTargetIp(), udpBaseInfo.getTargetPort()); + ByteBuf byteBuf = Unpooled.copiedBuffer(proxyMessage.getData()); + channel.writeAndFlush(new DatagramPacket(byteBuf, address)); + } + + @Override + public String name() { + return ProxyDataTypeEnum.UDP_TRANSFER.getDesc(); + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/solon/BeanHandler.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/solon/BeanHandler.java new file mode 100644 index 00000000..ed8f3fb6 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/solon/BeanHandler.java @@ -0,0 +1,19 @@ +package org.dromara.neutrinoproxy.client.sdk.solon; + +import org.dromara.neutrinoproxy.client.sdk.config.IBeanHandler; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; +import org.noear.solon.Solon; + + +public class BeanHandler implements IBeanHandler { + @Override + public Dispatcher getDispatcher(){ + return Solon.context().getBean(Dispatcher.class); + } + + @Override + public ProxyConfig getProxyConfig() { + return Solon.context().getBean(ProxyConfig.class); + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/solon/ProxyConfiguration.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/solon/ProxyConfiguration.java new file mode 100644 index 00000000..eaeddcf1 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/solon/ProxyConfiguration.java @@ -0,0 +1,119 @@ +package org.dromara.neutrinoproxy.client.sdk.solon; + +import com.google.common.collect.Lists; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.nio.NioEventLoopGroup; +import org.dromara.neutrinoproxy.client.sdk.config.IBeanHandler; +import org.dromara.neutrinoproxy.client.sdk.config.IProxyConfiguration; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.handler.*; +import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.dromara.neutrinoproxy.core.ProxyMessageHandler; +import org.dromara.neutrinoproxy.core.aot.NeutrinoCoreRuntimeNativeRegistrar; +import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher; +import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher; +import org.noear.solon.Solon; +import org.noear.solon.annotation.Bean; +import org.noear.solon.annotation.Configuration; +import org.noear.solon.annotation.Inject; +import org.noear.solon.core.bean.LifecycleBean; + +import java.util.List; + +/** + * 代理配置 + * @author: aoshiguchen + * @date: 2022/10/8 + */ +@Configuration +public class ProxyConfiguration extends IProxyConfiguration implements LifecycleBean { + @Inject + private ProxyConfig proxyConfig; + @Inject("tcpProxyTunnelBootstrap") + private Bootstrap tcpProxyTunnelBootstrap; + @Inject("realServerBootstrap") + private Bootstrap realServerBootstrap; + + @Override + public void start() throws Throwable { + List list = Lists.newArrayList( + new ProxyMessageAuthHandler(proxyConfig), + new ProxyMessageConnectHandler(tcpProxyTunnelBootstrap,realServerBootstrap,proxyConfig), + new ProxyMessageDisconnectHandler(), + new ProxyMessageErrorHandler(), + new ProxyMessageTransferHandler(), + new UdpProxyMessageConnectHandler(proxyConfig,tcpProxyTunnelBootstrap), + new UdpProxyMessageTransferHandler() + ); + Dispatcher dispatcher = new DefaultDispatcher<>("MessageDispatcher", list, + proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ? + null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName()); + Solon.context().wrapAndPut(Dispatcher.class, dispatcher); + } + + @Override + public IBeanHandler getBeanHandler() { + return new BeanHandler(); + } + @Bean("tunnelWorkGroup") + public NioEventLoopGroup tunnelWorkGroup(@Inject ProxyConfig proxyConfig) { + return super.tunnelWorkGroup(proxyConfig); + } + + @Bean("tcpRealServerWorkGroup") + public NioEventLoopGroup tcpRealServerWorkGroup(@Inject ProxyConfig proxyConfig) { + // 暂时先公用此配置 + return super.tcpRealServerWorkGroup(proxyConfig); + } + + @Bean("udpServerGroup") + public NioEventLoopGroup udpServerGroup(@Inject ProxyConfig proxyConfig) { + // 暂时先公用此配置 + return super.udpServerGroup(proxyConfig); + } + + @Bean("udpWorkGroup") + public NioEventLoopGroup udpWorkGroup(@Inject ProxyConfig proxyConfig) { + // 暂时先公用此配置 + return super.udpWorkGroup(proxyConfig); + } + + @Bean("cmdTunnelBootstrap") + public Bootstrap cmdTunnelBootstrap(@Inject ProxyConfig proxyConfig, + @Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) { + return super.cmdTunnelBootstrap(proxyConfig,tunnelWorkGroup); + } + + @Bean("tcpProxyTunnelBootstrap") + public Bootstrap tcpProxyTunnelBootstrap(@Inject ProxyConfig proxyConfig, + @Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) { + return super.tcpProxyTunnelBootstrap(proxyConfig,tunnelWorkGroup); + } + + @Bean("udpProxyTunnelBootstrap") + public Bootstrap udpProxyTunnelBootstrap(@Inject ProxyConfig proxyConfig, + @Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) { + return super.udpProxyTunnelBootstrap(proxyConfig,tunnelWorkGroup); + } + + @Bean("realServerBootstrap") + public Bootstrap realServerBootstrap(@Inject ProxyConfig proxyConfig, + @Inject("tcpRealServerWorkGroup") NioEventLoopGroup tcpRealServerWorkGroup + ) { + return super.realServerBootstrap(proxyConfig,tcpRealServerWorkGroup); + } + + @Bean("udpServerBootstrap") + public Bootstrap udpServerBootstrap(@Inject ProxyConfig proxyConfig, + @Inject("udpServerGroup") NioEventLoopGroup udpServerGroup, + @Inject("udpWorkGroup") NioEventLoopGroup udpWorkGroup) { + return super.udpServerBootstrap(proxyConfig,udpServerGroup,udpWorkGroup); + } + + @Bean + public NeutrinoCoreRuntimeNativeRegistrar neutrinoCoreRuntimeNativeRegistrar() { + return super.neutrinoCoreRuntimeNativeRegistrar(); + } +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/LockChannel.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/LockChannel.java new file mode 100644 index 00000000..765e0ba1 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/LockChannel.java @@ -0,0 +1,28 @@ +package org.dromara.neutrinoproxy.client.sdk.util; + +import io.netty.channel.Channel; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * @author: aoshiguchen + * @date: 2023/9/21 + */ +@Accessors(chain = true) +@Data +public class LockChannel { + // 端口号 + private int port; + // 通道 + private Channel channel; + // 期望的响应次数 + private int proxyResponses; + // 超时时间(毫秒) + private long proxyTimeoutMs; + // 被获取的时间 + private Date takeTime; + // 已经响应的次数 + private int responseCount; +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/ProxyUtil.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/ProxyUtil.java new file mode 100644 index 00000000..3e28da24 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/ProxyUtil.java @@ -0,0 +1,226 @@ +/** + * 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.sdk.util; + +import io.netty.channel.ChannelHandler; +import io.netty.handler.ssl.SslHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.core.ProxyChannelBorrowListener; +import org.dromara.neutrinoproxy.core.Constants; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelOption; +import io.netty.util.AttributeKey; +import org.dromara.neutrinoproxy.core.util.FileUtil; +import org.noear.solon.Solon; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.InputStream; +import java.security.KeyStore; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * 代理工具 + * @author: aoshiguchen + * @date: 2022/8/31 + */ +@Slf4j +public class ProxyUtil { + private static final AttributeKey USER_CHANNEL_WRITEABLE = AttributeKey.newInstance("user_channel_writeable"); + + private static final AttributeKey CLIENT_CHANNEL_WRITEABLE = AttributeKey.newInstance("client_channel_writeable"); + + private static final int MAX_POOL_SIZE = 100; + + private static Map realServerChannels = new ConcurrentHashMap(); + + private static ConcurrentLinkedQueue tcpProxyChannelPool = new ConcurrentLinkedQueue(); + private static ConcurrentLinkedQueue udpProxyChannelPool = new ConcurrentLinkedQueue<>(); + + private static volatile Channel cmdChannel; + + private static String clientId; + private static final String CLIENT_ID_FILE = ".NEUTRINO_PROXY_CLIENT_ID"; + + public static void borrowTcpProxyChanel(Bootstrap tcpProxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) { + Channel channel = tcpProxyChannelPool.poll(); + if (null != channel) { + borrowListener.success(channel); + return; + } + + tcpProxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> { + if (future.isSuccess()) { + borrowListener.success(future.channel()); + } else { + borrowListener.error(future.cause()); + } + }); + } + + public static void returnTcpProxyChanel(Channel proxyChanel) { + if (tcpProxyChannelPool.size() > MAX_POOL_SIZE) { + proxyChanel.close(); + } else { + proxyChanel.config().setOption(ChannelOption.AUTO_READ, true); + proxyChanel.attr(Constants.NEXT_CHANNEL).remove(); + tcpProxyChannelPool.offer(proxyChanel); + } + } + + + + public static void removeTcpProxyChanel(Channel proxyChanel) { + tcpProxyChannelPool.remove(proxyChanel); + } + + public static void borrowUdpProxyChanel(Bootstrap tcpProxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) { + Channel channel = udpProxyChannelPool.poll(); + if (null != channel) { + borrowListener.success(channel); + return; + } + + tcpProxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> { + if (future.isSuccess()) { + borrowListener.success(future.channel()); + } else { + borrowListener.error(future.cause()); + } + }); + } + + public static void returnUdpProxyChanel(Channel proxyChanel) { + if (udpProxyChannelPool.size() > MAX_POOL_SIZE) { + proxyChanel.close(); + } else { + proxyChanel.config().setOption(ChannelOption.AUTO_READ, true); + proxyChanel.attr(Constants.NEXT_CHANNEL).remove(); + udpProxyChannelPool.offer(proxyChanel); + } + } + + + + public static void removeUdpProxyChanel(Channel proxyChanel) { + udpProxyChannelPool.remove(proxyChanel); + } + + public static void setCmdChannel(Channel cmdChannel) { + ProxyUtil.cmdChannel = cmdChannel; + } + + public static Channel getCmdChannel() { + return cmdChannel; + } + + public static void setRealServerChannelVisitorId(Channel realServerChannel, String visitorId) { + realServerChannel.attr(Constants.VISITOR_ID).set(visitorId); + } + + public static String getVisitorIdByRealServerChannel(Channel realServerChannel) { + return realServerChannel.attr(Constants.VISITOR_ID).get(); + } + + public static Channel getRealServerChannel(String userId) { + return realServerChannels.get(userId); + } + + public static void addRealServerChannel(String userId, Channel realServerChannel) { + realServerChannels.put(userId, realServerChannel); + } + + public static Channel removeRealServerChannel(String userId) { + return realServerChannels.remove(userId); + } + + public static boolean isRealServerReadable(Channel realServerChannel) { + return realServerChannel.attr(CLIENT_CHANNEL_WRITEABLE).get() && realServerChannel.attr(USER_CHANNEL_WRITEABLE).get(); + } + + public static void clearRealServerChannels() { + Iterator> ite = realServerChannels.entrySet().iterator(); + while (ite.hasNext()) { + Channel realServerChannel = ite.next().getValue(); + if (realServerChannel.isActive()) { + realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); + } + } + + realServerChannels.clear(); + } + + public static String getClientId() { + if (StringUtils.isNotBlank(clientId)) { + return clientId; + } + ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class); + if (StringUtils.isNotBlank(proxyConfig.getTunnel().getClientId())) { + clientId = proxyConfig.getTunnel().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; + } + + public static ChannelHandler createSslHandler(ProxyConfig proxyConfig) { + try { + InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath()); + + SSLContext clientContext = SSLContext.getInstance("TLS"); + final KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + TrustManager[] trustManagers = tmf.getTrustManagers(); + clientContext.init(null, trustManagers, null); + + SSLEngine sslEngine = clientContext.createSSLEngine(); + sslEngine.setUseClientMode(true); + + return new SslHandler(sslEngine); + } catch (Exception e) { + log.error("create SSL handler failed", e); + e.printStackTrace(); + } + return null; + } + +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/UdpChannelBindInfo.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/UdpChannelBindInfo.java new file mode 100644 index 00000000..21fa5366 --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/UdpChannelBindInfo.java @@ -0,0 +1,22 @@ +package org.dromara.neutrinoproxy.client.sdk.util; + +import io.netty.channel.Channel; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author: aoshiguchen + * @date: 2023/9/21 + */ +@Accessors(chain = true) +@Data +public class UdpChannelBindInfo { + private Channel tunnelChannel; + private LockChannel lockChannel; + private String visitorId; + private String visitorIp; + private int visitorPort; + private int serverPort; + private String targetIp; + private int targetPort; +} diff --git a/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/UdpServerUtil.java b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/UdpServerUtil.java new file mode 100644 index 00000000..ba909d6d --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/java/org/dromara/neutrinoproxy/client/sdk/util/UdpServerUtil.java @@ -0,0 +1,201 @@ +package org.dromara.neutrinoproxy.client.sdk.util; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.dromara.neutrinoproxy.client.sdk.config.ProxyConfig; +import org.dromara.neutrinoproxy.client.sdk.constant.Constants; +import org.dromara.neutrinoproxy.client.sdk.core.CustomThreadFactory; +import org.dromara.neutrinoproxy.core.ProxyMessage; +import org.noear.solon.core.runtime.NativeDetector; + +import java.util.*; +import java.util.concurrent.*; + +/** + * @author: aoshiguchen + * @date: 2023/9/21 + */ +@Slf4j +public class UdpServerUtil { + private static final Boolean isSupportUdp = Boolean.FALSE; + private static int udpServerPortMin = 0; + private static int udpServerPortMax = 0; + private static int nextUdpServerPort = 0; + private static Bootstrap udpServerBootstrap; + private static int defaultUdpServerPort; + private static Channel defaultUdpServerChannel; + private static Map portToChannelMap = new ConcurrentHashMap<>(); + /** + * udp服务空闲端口池 + */ + private static ConcurrentLinkedQueue udpServerFreePortPool = new ConcurrentLinkedQueue<>(); + private static List lockChannelList = new ArrayList<>(); + /** + * lockChannel扫描器 + */ + private static final ScheduledExecutorService lockChannelScanner = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("lockChannelScanner")); + + /** + * 初始化UDP缓存 + * 1、初始化一个基础UDP服务,用于不需要响应的UDP转发 + * 2、维护一个UDP服务池,用于需要响应的UDP转发 + * @param proxyConfig + */ + public static void initCache(ProxyConfig proxyConfig, Bootstrap udpServerBootstrap) { + // aot 阶段,不初始化UDP服务 + if (NativeDetector.isAotRuntime()) { + return; + } + if (null == proxyConfig.getClient().getUdp() || StringUtils.isEmpty(proxyConfig.getClient().getUdp().getPuppetPortRange())) { + return; + } + ProxyConfig.Udp udpConfig = proxyConfig.getClient().getUdp(); + if (StringUtils.isEmpty(udpConfig.getPuppetPortRange())) { + return; + } + String[] tmp = udpConfig.getPuppetPortRange().split("-"); + if (null == tmp || tmp.length != 2) { + log.error("client udp config error!"); + return; + } + try { + udpServerPortMin = Integer.parseInt(tmp[0]); + udpServerPortMax = Integer.parseInt(tmp[1]); + if (udpServerPortMax <= udpServerPortMin) { + // 至少得给1个udp端口,一个用于基础无响应UDP转发 + throw new RuntimeException("client udp config error!"); + } + nextUdpServerPort = udpServerPortMin; + UdpServerUtil.udpServerBootstrap = udpServerBootstrap; + log.info("udp proxy server port: {} ~ {}", udpServerPortMin, udpServerPortMax); + // 初始化udp服务 + initUdpServer(); + // 初始化lockChannel扫描器 + lockChannelScanner.scheduleWithFixedDelay(UdpServerUtil::lockChannelScan, 5, 3, TimeUnit.SECONDS); + } catch (Exception e) { + log.error("client udp config error!", e); + return; + } + } + + /** + * 初始化udp服务 + */ + private static void initUdpServer() { + defaultUdpServerPort = nextUdpServerPort(); + defaultUdpServerChannel = bindPort(defaultUdpServerPort); + // 初始化默认最多额外开启5个udp服务,其他的需要时再启动 + for (int i = 0; i < 5; i++) { + if (!hasNextUdpServerPort()) { + return; + } + int port = nextUdpServerPort(); + Channel ch = bindPort(port); + portToChannelMap.put(port, ch); + udpServerFreePortPool.offer(port); + } + while (hasNextUdpServerPort()) { + udpServerFreePortPool.offer(nextUdpServerPort()); + } + } + + private static Channel bindPort(int port) { + try { + ChannelFuture channelFuture = udpServerBootstrap.bind(port).sync(); + log.info("[udp server] bind port:{} success!", port); + return channelFuture.channel(); + } catch (InterruptedException e) { + log.error("[udp server] bind port:{} error!", port); + throw new RuntimeException(e); + } + } + + public static Boolean hasNextUdpServerPort() { + return nextUdpServerPort <= udpServerPortMax; + } + + public static synchronized int nextUdpServerPort() { + return nextUdpServerPort++; + } + + /** + * 获取一个可用的udp通道 + * 1、如果期待的响应为0,或者超时时间<=0,则认为不需要响应,直接返回默认的udp服务,否则继续下一步 + * 2、从可用端口队列中找到一个可用端口,若不存在可用端口,则降级为不需要响应,返回默认的udp服务。否则继续下一步 + * 3、根据该端口找到udp服务通道,找不到则绑定端口开启一个通道并返回。将该端口添加到锁定列表 + * 4、维护一个定时器的,定时扫描锁定列表,及时释放锁定的端口 + * @param info + * @return + */ + public static synchronized Channel takeChannel(ProxyMessage.UdpBaseInfo info, Channel tunnelChannel) { + if (info.getProxyResponses() <= 0 || info.getProxyTimeoutMs() <= 0) { + return defaultUdpServerChannel; + } + Integer port = udpServerFreePortPool.poll(); + if (null == port) { + return defaultUdpServerChannel; + } + Channel channel = portToChannelMap.get(port); + if (null == channel) { + channel = bindPort(port); + portToChannelMap.put(port, channel); + } + // 添加到锁定队列 + LockChannel lockChannel = new LockChannel() + .setPort(port) + .setChannel(channel) + .setProxyResponses(info.getProxyResponses()) + .setProxyTimeoutMs(info.getProxyTimeoutMs()) + .setTakeTime(new Date()) + .setResponseCount(0); + lockChannelList.add(lockChannel); + channel.attr(Constants.UDP_CHANNEL_BIND_KEY).set(new UdpChannelBindInfo() + .setTunnelChannel(tunnelChannel) + .setVisitorId(info.getVisitorId()) + .setVisitorIp(info.getVisitorIp()) + .setVisitorPort(info.getVisitorPort()) + .setServerPort(info.getServerPort()) + .setTargetIp(info.getTargetIp()) + .setTargetPort(info.getTargetPort()) + .setLockChannel(lockChannel) + ); + return channel; + } + + /** + * lockChannel扫描 + */ + public static synchronized void lockChannelScan() { + if (lockChannelList.isEmpty()) { + return; + } + Iterator iter = lockChannelList.iterator(); + if (iter.hasNext()) { + LockChannel lockChannel = iter.next(); + if (lockChannel.getResponseCount() >= lockChannel.getProxyResponses() || + System.currentTimeMillis() - lockChannel.getTakeTime().getTime() >= lockChannel.getProxyTimeoutMs() + ) { + iter.remove(); + UdpChannelBindInfo udpChannelBindInfo = lockChannel.getChannel().attr(Constants.UDP_CHANNEL_BIND_KEY).get(); + // 此处必须释放代理隧道 + closeChannel(udpChannelBindInfo.getTunnelChannel()); + lockChannel.getChannel().attr(Constants.UDP_CHANNEL_BIND_KEY).set(null); + udpServerFreePortPool.offer(lockChannel.getPort()); + log.debug("[udp channel]release udp channel port:{}", lockChannel.getPort()); + } + } + } + + private static void closeChannel(Channel channel) { + try { + channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); + } catch (Exception e) { + // ignore + } + } +} diff --git a/neutrino-proxy-client-sdk/src/main/resources/app.yml b/neutrino-proxy-client-sdk/src/main/resources/app.yml new file mode 100644 index 00000000..a122416c --- /dev/null +++ b/neutrino-proxy-client-sdk/src/main/resources/app.yml @@ -0,0 +1,68 @@ +solon: + config: + add: ./app.yml + app: + name: neutrino-proxy-client + version: 2.0.1 +# 日志级别 +solon.logging.appender: + console: + pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(${PID:-}) --- %-15([%15.15thread]) %-56(%cyan(%-40.40logger{39}%L)) : %msg%n" + file: + enable: true + pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:-} --- %-15([%15.15thread]) %-56(%-40.40logger{39}%L) : %msg%n" + name: "logs/${neutrino.application.name}" + rolling: "logs/${neutrino.application.name}_%d{yyyy-MM-dd}_%i.log.gz" +solon.logging.logger: + "root": + level: info + +neutrino: + 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: 120 + write-idle-time: 20 + all-idle-time-seconds: 0 + tunnel: + # 线程池相关配置,用于技术调优,可忽略 + 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} + # 是否开启心跳日志 + heartbeat-log-enable: ${HEARTBEAT_LOG:false} + # 重连设置 + reconnection: + # 重连间隔(秒) + interval-seconds: 10 + # 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启) + unlimited: false + client: + udp: + # 线程池相关配置,用于技术调优,可忽略 + boss-thread-count: 5 + work-thread-count: 20 + # udp傀儡端口范围 + puppet-port-range: 10000-10500 + # 是否开启隧道传输报文日志(日志级别为debug时开启才有效) + transfer-log-enable: ${CLIENT_LOG:false} diff --git a/neutrino-proxy-client-sdk/src/main/resources/test.jks b/neutrino-proxy-client-sdk/src/main/resources/test.jks new file mode 100644 index 00000000..3a4d1ad8 Binary files /dev/null and b/neutrino-proxy-client-sdk/src/main/resources/test.jks differ diff --git a/pom.xml b/pom.xml index 46b7b52b..70cd80d5 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ neutrino-proxy-core neutrino-proxy-client neutrino-proxy-server + neutrino-proxy-client-sdk