Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afff036641 | ||
|
|
9944e60825 | ||
|
|
74ff0c3b44 | ||
|
|
3e9e7cb370 |
+1
-2
@@ -64,8 +64,7 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||||
// TODO 该事件有50%的概率不触发
|
|
||||||
System.out.println("11");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+19
-5
@@ -18,10 +18,15 @@ public class ProxyConfig {
|
|||||||
@Inject("${neutrino.proxy.protocol}")
|
@Inject("${neutrino.proxy.protocol}")
|
||||||
private Protocol protocol;
|
private Protocol protocol;
|
||||||
/**
|
/**
|
||||||
* 服务端配置
|
* 代理服务配置
|
||||||
*/
|
*/
|
||||||
@Inject("${neutrino.proxy.server}")
|
@Inject("${neutrino.proxy.server}")
|
||||||
private Server server;
|
private Server server;
|
||||||
|
/**
|
||||||
|
* 代理隧道配置
|
||||||
|
*/
|
||||||
|
@Inject("${neutrino.proxy.tunnel}")
|
||||||
|
private Tunnel tunnel;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Protocol {
|
public static class Protocol {
|
||||||
@@ -37,15 +42,24 @@ public class ProxyConfig {
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class Server {
|
public static class Server {
|
||||||
|
private Integer bossThreadCount;
|
||||||
|
private Integer workThreadCount;
|
||||||
|
private String domainName;
|
||||||
|
private Integer httpProxyPort;
|
||||||
|
private Integer httpsProxyPort;
|
||||||
|
private String keyStorePassword;
|
||||||
|
private String jksPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Tunnel {
|
||||||
|
private Integer bossThreadCount;
|
||||||
|
private Integer workThreadCount;
|
||||||
private Integer port;
|
private Integer port;
|
||||||
private Integer sslPort;
|
private Integer sslPort;
|
||||||
private String keyStorePassword;
|
private String keyStorePassword;
|
||||||
private String keyManagerPassword;
|
private String keyManagerPassword;
|
||||||
private String jksPath;
|
private String jksPath;
|
||||||
private Integer bossThreadCount;
|
|
||||||
private Integer workThreadCount;
|
|
||||||
private String domainName;
|
|
||||||
private Integer httpProxyPort;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -43,4 +43,14 @@ public class ProxyConfiguration implements LifecycleBean {
|
|||||||
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
|
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean("tunnelBossGroup")
|
||||||
|
public NioEventLoopGroup tunnelBossGroup(@Inject ProxyConfig proxyConfig) {
|
||||||
|
return new NioEventLoopGroup(proxyConfig.getTunnel().getBossThreadCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("tunnelWorkerGroup")
|
||||||
|
public NioEventLoopGroup tunnelWorkerGroup(@Inject ProxyConfig proxyConfig) {
|
||||||
|
return new NioEventLoopGroup(proxyConfig.getTunnel().getWorkThreadCount());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-150
@@ -2,28 +2,17 @@ package org.dromara.neutrinoproxy.server.proxy.core;
|
|||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import io.netty.bootstrap.ServerBootstrap;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.channel.*;
|
import io.netty.channel.*;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.dromara.neutrinoproxy.core.Constants;
|
|
||||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
|
||||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
|
||||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
|
||||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
|
||||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
|
||||||
import org.noear.solon.Solon;
|
|
||||||
import org.noear.solon.annotation.Component;
|
import org.noear.solon.annotation.Component;
|
||||||
import org.noear.solon.annotation.Inject;
|
import org.noear.solon.annotation.Inject;
|
||||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||||
import org.noear.solon.core.event.EventListener;
|
import org.noear.solon.core.event.EventListener;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author: aoshiguchen
|
* @author: aoshiguchen
|
||||||
* @date: 2023/4/2
|
* @date: 2023/4/2
|
||||||
@@ -54,7 +43,7 @@ public class HttpProxy implements EventListener<AppLoadEndEvent> {
|
|||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||||
ch.pipeline().addLast(new VisitorChannelHandler());
|
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
|
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
|
||||||
@@ -63,142 +52,4 @@ public class HttpProxy implements EventListener<AppLoadEndEvent> {
|
|||||||
log.error("http proxy start err!", e);
|
log.error("http proxy start err!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
|
|
||||||
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytes = new byte[byteBuf.readableBytes()];
|
|
||||||
byteBuf.readBytes(bytes);
|
|
||||||
byteBuf.resetReaderIndex();
|
|
||||||
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
|
|
||||||
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
|
|
||||||
if (null == proxyChannel) {
|
|
||||||
// 该端口还没有代理客户端
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(ProxyUtil.getVisitorIdByChannel(channel), bytes));
|
|
||||||
|
|
||||||
// 增加流量计数
|
|
||||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(channel);
|
|
||||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
String visitorId = ProxyUtil.getVisitorIdByChannel(ctx.channel());
|
|
||||||
if (StringUtils.isNotBlank(visitorId)) {
|
|
||||||
proxyAttachment.execute();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
|
||||||
ctx.channel().config().setOption(ChannelOption.AUTO_READ, false);
|
|
||||||
|
|
||||||
String host = getHost(bytes);
|
|
||||||
if (StringUtils.isBlank(host)) {
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.debug("HttpProxy host: {}", host);
|
|
||||||
if (!host.endsWith(proxyConfig.getServer().getDomainName())) {
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int index = host.lastIndexOf("." + proxyConfig.getServer().getDomainName());
|
|
||||||
String subdomain = host.substring(0, index);
|
|
||||||
|
|
||||||
// 根据域名拿到绑定的映射对应的cmdChannel
|
|
||||||
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
|
|
||||||
if (null == serverPort) {
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
|
|
||||||
if (null == cmdChannel) {
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
|
|
||||||
if (StringUtils.isBlank(lanInfo)) {
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
visitorId = ProxyUtil.newVisitorId();
|
|
||||||
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, ctx.channel(), serverPort);
|
|
||||||
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
|
|
||||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
|
|
||||||
// 通知代理客户端
|
|
||||||
Channel visitorChannel = ctx.channel();
|
|
||||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
|
||||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
|
||||||
|
|
||||||
if (cmdChannel == null) {
|
|
||||||
// 该端口还没有代理客户端
|
|
||||||
ctx.channel().close();
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// 用户连接断开,从控制连接中移除
|
|
||||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
|
||||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
|
||||||
|
|
||||||
// 删除代理附加对象
|
|
||||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
|
||||||
|
|
||||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
|
||||||
if (proxyChannel != null && proxyChannel.isActive()) {
|
|
||||||
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
|
||||||
proxyChannel.attr(Constants.LICENSE_ID).remove();
|
|
||||||
proxyChannel.attr(Constants.VISITOR_ID).remove();
|
|
||||||
|
|
||||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
|
||||||
// 通知客户端,用户连接已经断开
|
|
||||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.channelInactive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
super.channelActive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
||||||
// 当出现异常就关闭连接
|
|
||||||
ctx.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getHost(byte[] buf) {
|
|
||||||
String req = new String(buf);
|
|
||||||
String[] lines = req.split("\r\n");
|
|
||||||
String firstLine = lines[0];
|
|
||||||
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (int i = 1; i < lines.length; i++) {
|
|
||||||
String line = lines[i];
|
|
||||||
if (!line.startsWith("Host: ")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 域名
|
|
||||||
String domain = line.substring(6);
|
|
||||||
return domain;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
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;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dromara.neutrinoproxy.core.Constants;
|
||||||
|
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||||
|
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||||
|
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||||
|
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||||
|
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||||
|
import org.noear.solon.Solon;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: aoshiguchen
|
||||||
|
* @date: 2023/5/27
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||||
|
/**
|
||||||
|
* 域名
|
||||||
|
*/
|
||||||
|
private String domainName;
|
||||||
|
|
||||||
|
public HttpVisitorChannelHandler(String domainName) {
|
||||||
|
this.domainName = domainName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
|
||||||
|
if (StrUtil.isBlank(domainName)) {
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[byteBuf.readableBytes()];
|
||||||
|
byteBuf.readBytes(bytes);
|
||||||
|
byteBuf.resetReaderIndex();
|
||||||
|
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
|
||||||
|
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
|
||||||
|
if (null == proxyChannel) {
|
||||||
|
// 该端口还没有代理客户端
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(ProxyUtil.getVisitorIdByChannel(channel), bytes));
|
||||||
|
|
||||||
|
// 增加流量计数
|
||||||
|
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(channel);
|
||||||
|
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
String visitorId = ProxyUtil.getVisitorIdByChannel(ctx.channel());
|
||||||
|
if (StringUtils.isNotBlank(visitorId)) {
|
||||||
|
proxyAttachment.execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||||
|
ctx.channel().config().setOption(ChannelOption.AUTO_READ, false);
|
||||||
|
|
||||||
|
String host = getHost(bytes);
|
||||||
|
if (StringUtils.isBlank(host)) {
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug("HttpProxy host: {}", host);
|
||||||
|
if (!host.endsWith(domainName)) {
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = host.lastIndexOf("." + domainName);
|
||||||
|
String subdomain = host.substring(0, index);
|
||||||
|
|
||||||
|
// 根据域名拿到绑定的映射对应的cmdChannel
|
||||||
|
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
|
||||||
|
if (null == serverPort) {
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
|
||||||
|
if (null == cmdChannel) {
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
|
||||||
|
if (StringUtils.isBlank(lanInfo)) {
|
||||||
|
ctx.channel().close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorId = ProxyUtil.newVisitorId();
|
||||||
|
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, ctx.channel(), serverPort);
|
||||||
|
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
|
||||||
|
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
|
||||||
|
// 通知代理客户端
|
||||||
|
Channel visitorChannel = ctx.channel();
|
||||||
|
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||||
|
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||||
|
|
||||||
|
if (cmdChannel == null) {
|
||||||
|
// 该端口还没有代理客户端
|
||||||
|
ctx.channel().close();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 用户连接断开,从控制连接中移除
|
||||||
|
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||||
|
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||||
|
|
||||||
|
// 删除代理附加对象
|
||||||
|
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||||
|
|
||||||
|
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||||
|
if (proxyChannel != null && proxyChannel.isActive()) {
|
||||||
|
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
||||||
|
proxyChannel.attr(Constants.LICENSE_ID).remove();
|
||||||
|
proxyChannel.attr(Constants.VISITOR_ID).remove();
|
||||||
|
|
||||||
|
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||||
|
// 通知客户端,用户连接已经断开
|
||||||
|
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
// 当出现异常就关闭连接
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHost(byte[] buf) {
|
||||||
|
String req = new String(buf);
|
||||||
|
String[] lines = req.split("\r\n");
|
||||||
|
String firstLine = lines[0];
|
||||||
|
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < lines.length; i++) {
|
||||||
|
String line = lines[i];
|
||||||
|
if (!line.startsWith("Host: ")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 域名
|
||||||
|
String domain = line.substring(6);
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.ssl.SslHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dromara.neutrinoproxy.core.util.FileUtil;
|
||||||
|
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||||
|
import org.noear.solon.annotation.Component;
|
||||||
|
import org.noear.solon.annotation.Inject;
|
||||||
|
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||||
|
import org.noear.solon.core.event.EventListener;
|
||||||
|
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: aoshiguchen
|
||||||
|
* @date: 2023/4/2
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class HttpsProxy implements EventListener<AppLoadEndEvent> {
|
||||||
|
@Inject("serverBossGroup")
|
||||||
|
private NioEventLoopGroup serverBossGroup;
|
||||||
|
@Inject("serverWorkerGroup")
|
||||||
|
private NioEventLoopGroup serverWorkerGroup;
|
||||||
|
@Inject
|
||||||
|
private ProxyConfig proxyConfig;
|
||||||
|
@Override
|
||||||
|
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||||
|
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName()) || null == proxyConfig.getServer().getHttpsProxyPort() ||
|
||||||
|
StringUtils.isEmpty(proxyConfig.getServer().getJksPath()) || StringUtils.isEmpty(proxyConfig.getServer().getKeyStorePassword())) {
|
||||||
|
log.info("no config domain name,nonsupport https proxy.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start() {
|
||||||
|
try {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
|
bootstrap.group(serverBossGroup, serverWorkerGroup)
|
||||||
|
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
|
@Override
|
||||||
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
|
ch.pipeline().addLast(createSslHandler());
|
||||||
|
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||||
|
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync();
|
||||||
|
log.info("Https代理服务启动成功!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("https proxy start err!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelHandler createSslHandler() {
|
||||||
|
try {
|
||||||
|
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getJksPath());
|
||||||
|
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||||
|
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||||
|
|
||||||
|
ks.load(jksInputStream, proxyConfig.getServer().getKeyStorePassword().toCharArray());
|
||||||
|
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmf.init(ks, proxyConfig.getServer().getKeyStorePassword().toCharArray());
|
||||||
|
TrustManager[] trustManagers = null;
|
||||||
|
|
||||||
|
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||||
|
|
||||||
|
SSLEngine sslEngine = serverContext.createSSLEngine();
|
||||||
|
sslEngine.setUseClientMode(false);
|
||||||
|
sslEngine.setNeedClientAuth(false);
|
||||||
|
|
||||||
|
return new SslHandler(sslEngine);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建SSL处理器失败", e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-20
@@ -36,20 +36,10 @@ import java.security.KeyStore;
|
|||||||
public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
||||||
@Inject
|
@Inject
|
||||||
private ProxyConfig proxyConfig;
|
private ProxyConfig proxyConfig;
|
||||||
@Inject("serverBossGroup")
|
@Inject("tunnelBossGroup")
|
||||||
private NioEventLoopGroup serverBossGroup;
|
private NioEventLoopGroup serverBossGroup;
|
||||||
@Inject("serverWorkerGroup")
|
@Inject("tunnelWorkerGroup")
|
||||||
private NioEventLoopGroup serverWorkerGroup;
|
private NioEventLoopGroup serverWorkerGroup;
|
||||||
@Inject("${neutrino.proxy.server.port}")
|
|
||||||
private Integer port;
|
|
||||||
@Inject("${neutrino.proxy.server.ssl-port}")
|
|
||||||
private Integer sslPort;
|
|
||||||
@Inject("${neutrino.proxy.server.jks-path}")
|
|
||||||
private String jksPath;
|
|
||||||
@Inject("${neutrino.proxy.server.key-store-password}")
|
|
||||||
private String keyStorePassword;
|
|
||||||
@Inject("${neutrino.proxy.server.key-manager-password}")
|
|
||||||
private String keyManagerPassword;
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||||
startProxyServer();
|
startProxyServer();
|
||||||
@@ -68,15 +58,15 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
bootstrap.bind(port).sync();
|
bootstrap.bind(proxyConfig.getTunnel().getPort()).sync();
|
||||||
log.info("代理服务启动,端口:{}", port);
|
log.info("代理服务启动,端口:{}", proxyConfig.getTunnel().getPort());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("代理服务异常", e);
|
log.error("代理服务异常", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startProxyServerForSSL() {
|
private void startProxyServerForSSL() {
|
||||||
if (null == sslPort) {
|
if (null == proxyConfig.getTunnel().getSslPort()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
@@ -89,8 +79,8 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
bootstrap.bind(sslPort).sync();
|
bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync();
|
||||||
log.info("代理服务启动,SSL端口: {}", sslPort);
|
log.info("代理服务启动,SSL端口: {}", proxyConfig.getTunnel().getSslPort());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("代理服务异常", e);
|
log.error("代理服务异常", e);
|
||||||
}
|
}
|
||||||
@@ -98,13 +88,13 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
|||||||
|
|
||||||
private ChannelHandler createSslHandler() {
|
private ChannelHandler createSslHandler() {
|
||||||
try {
|
try {
|
||||||
InputStream jksInputStream = FileUtil.getInputStream(jksPath);
|
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
|
||||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||||
|
|
||||||
ks.load(jksInputStream, keyStorePassword.toCharArray());
|
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
|
||||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
kmf.init(ks, keyManagerPassword.toCharArray());
|
kmf.init(ks, proxyConfig.getTunnel().getKeyManagerPassword().toCharArray());
|
||||||
TrustManager[] trustManagers = null;
|
TrustManager[] trustManagers = null;
|
||||||
|
|
||||||
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||||
|
|||||||
+1
-2
@@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.neutrinoproxy.core.Constants;
|
import org.dromara.neutrinoproxy.core.Constants;
|
||||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
|
||||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||||
@@ -23,7 +22,7 @@ import java.net.InetSocketAddress;
|
|||||||
* @date: 2022/6/16
|
* @date: 2022/6/16
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
+2
-1
@@ -2,6 +2,7 @@ package org.dromara.neutrinoproxy.server.service;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||||
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
|
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
|
||||||
import org.noear.solon.annotation.Component;
|
import org.noear.solon.annotation.Component;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ public class ProtocalService {
|
|||||||
public List<ProtocalListRes> list() {
|
public List<ProtocalListRes> list() {
|
||||||
return Lists.newArrayList(
|
return Lists.newArrayList(
|
||||||
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
|
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
|
||||||
new ProtocalListRes().setName("HTTP").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP"),
|
new ProtocalListRes().setName("HTTP(S)").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP。 若配置了证书,则同时支持HTTPS。"),
|
||||||
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
|
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -14,7 +14,7 @@ import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
|||||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||||
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
||||||
import org.dromara.neutrinoproxy.server.proxy.core.VisitorChannelHandler;
|
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
|
||||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
|
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
|
||||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||||
@@ -224,7 +224,7 @@ public class VisitorChannelService {
|
|||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||||
ch.pipeline().addLast(new VisitorChannelHandler());
|
ch.pipeline().addLast(new TcpVisitorChannelHandler());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,17 +12,23 @@ neutrino:
|
|||||||
read-idle-time: 40
|
read-idle-time: 40
|
||||||
write-idle-time: 5
|
write-idle-time: 5
|
||||||
all-idle-time-seconds: 0
|
all-idle-time-seconds: 0
|
||||||
server:
|
tunnel:
|
||||||
boss-thread-count: 10
|
boss-thread-count: 2
|
||||||
work-thread-count: 60
|
work-thread-count: 10
|
||||||
port: ${OPEN_PORT:9000}
|
port: ${OPEN_PORT:9000}
|
||||||
ssl-port: ${SSL_PORT:9002}
|
ssl-port: ${SSL_PORT:9002}
|
||||||
key-store-password: ${STORE_PASS:123456}
|
key-store-password: ${STORE_PASS:123456}
|
||||||
key-manager-password: ${MGR_PASS:123456}
|
key-manager-password: ${MGR_PASS:123456}
|
||||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||||
|
server:
|
||||||
|
boss-thread-count: 5
|
||||||
|
work-thread-count: 20
|
||||||
http-proxy-port: ${HTTP_PROXY_PORT:80}
|
http-proxy-port: ${HTTP_PROXY_PORT:80}
|
||||||
|
https-proxy-port: ${HTTPS_PROXY_PORT:443}
|
||||||
# 如果不配置,则不支持域名映射
|
# 如果不配置,则不支持域名映射
|
||||||
domain-name: ${DOMAIN_NAME:}
|
domain-name: ${DOMAIN_NAME:}
|
||||||
|
key-store-password: ${HTTPS_STORE_PASS:}
|
||||||
|
jks-path: ${HTTPS_JKS_PATH:}
|
||||||
data:
|
data:
|
||||||
db:
|
db:
|
||||||
type: ${DB_TYPE:sqlite}
|
type: ${DB_TYPE:sqlite}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
|
|||||||
fi
|
fi
|
||||||
rm -f $OUT
|
rm -f $OUT
|
||||||
cd $JAR_PATH
|
cd $JAR_PATH
|
||||||
nohup java $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
|
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
|
||||||
echo "sleep 15s wating service start"
|
echo "sleep 15s wating service start"
|
||||||
sleep 15
|
sleep 15
|
||||||
tail -200 $OUT
|
tail -200 $OUT
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
|
|||||||
fi
|
fi
|
||||||
rm -f $OUT
|
rm -f $OUT
|
||||||
cd $JAR_PATH
|
cd $JAR_PATH
|
||||||
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
|
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
|
||||||
echo "sleep 15s wating service start"
|
echo "sleep 15s wating service start"
|
||||||
sleep 15
|
sleep 15
|
||||||
tail -200 $OUT
|
tail -200 $OUT
|
||||||
|
|||||||
Reference in New Issue
Block a user