初步拆分SDK

This commit is contained in:
xgc
2024-01-20 22:37:52 +08:00
parent 223ab4a447
commit 3596d147b8
34 changed files with 2135 additions and 0 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

+16
View File
@@ -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
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>neutrino-proxy</artifactId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>neutrino-proxy-client-sdk</artifactId>
<dependencies>
<dependency>
<groupId>org.dromara.neutrino-proxy</groupId>
<artifactId>neutrino-proxy-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-lib</artifactId>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>*.yml</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>app-dev.yml</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- 配置打包插件(并打包成胖包) -->
<plugin>
<groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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);
}
}
}
@@ -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();
}
@@ -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<SocketChannel>() {
@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<SocketChannel>() {
@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<SocketChannel>() {
@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<SocketChannel>() {
@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<NioDatagramChannel>() {
@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();
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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<UdpChannelBindInfo> UDP_CHANNEL_BIND_KEY = AttributeKey.newInstance("udpChannelBindKey");
}
@@ -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<ProxyMessage>{
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;
}
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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);
}
@@ -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();
}
}
@@ -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<ByteBuf> {
@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);
}
}
@@ -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<ProxyMessage> {
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;
}
}
}
}
@@ -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<ProxyMessage> {
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;
}
}
}
}
@@ -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<DatagramPacket> {
@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);
}
}
}
@@ -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();
}
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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<ProxyMessageHandler> list = Lists.newArrayList(
new ProxyMessageAuthHandler(proxyConfig),
new ProxyMessageConnectHandler(tcpProxyTunnelBootstrap,realServerBootstrap,proxyConfig),
new ProxyMessageDisconnectHandler(),
new ProxyMessageErrorHandler(),
new ProxyMessageTransferHandler(),
new UdpProxyMessageConnectHandler(proxyConfig,tcpProxyTunnelBootstrap),
new UdpProxyMessageTransferHandler()
);
Dispatcher<ChannelHandlerContext, ProxyMessage> 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();
}
}
@@ -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;
}
@@ -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<Boolean> USER_CHANNEL_WRITEABLE = AttributeKey.newInstance("user_channel_writeable");
private static final AttributeKey<Boolean> CLIENT_CHANNEL_WRITEABLE = AttributeKey.newInstance("client_channel_writeable");
private static final int MAX_POOL_SIZE = 100;
private static Map<String, Channel> realServerChannels = new ConcurrentHashMap<String, Channel>();
private static ConcurrentLinkedQueue<Channel> tcpProxyChannelPool = new ConcurrentLinkedQueue<Channel>();
private static ConcurrentLinkedQueue<Channel> 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<Map.Entry<String, Channel>> 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;
}
}
@@ -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;
}
@@ -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<Integer, Channel> portToChannelMap = new ConcurrentHashMap<>();
/**
* udp服务空闲端口池
*/
private static ConcurrentLinkedQueue<Integer> udpServerFreePortPool = new ConcurrentLinkedQueue<>();
private static List<LockChannel> 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<LockChannel> 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
}
}
}
@@ -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}
+1
View File
@@ -20,6 +20,7 @@
<module>neutrino-proxy-core</module>
<module>neutrino-proxy-client</module>
<module>neutrino-proxy-server</module>
<module>neutrino-proxy-client-sdk</module>
</modules>
<properties>