diff --git a/README.md b/README.md index 1a2722c4..f00a9a1f 100644 --- a/README.md +++ b/README.md @@ -47,38 +47,52 @@ keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks" ``` -## 5.2、修改服务端配置(application.yml) +## 5.2、修改服务端配置(app.yml) ```yml application: name: neutrino-proxy-server -proxy: - protocol: - max-frame-length: 2097152 - length-field-offset: 0 - length-field-length: 4 - initial-bytes-to-strip: 0 - length-adjustment: 0 - read-idle-time: 60 - write-idle-time: 40 - all-idle-time-seconds: 0 - server: - # 服务端端口,用于保持与客户端的连接,非SSL - port: 9000 - # 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置 - ssl-port: 9002 - # 证书密码 - key-store-password: 123456 - key-manager-password: 123456 - # 证书存放路径,若不想打进jar包,可不带classpath:前缀 - jks-path: classpath:/test.jks - data: - # 数据库配置(支持mysql) - type: sqlite - url: jdbc:sqlite:data.db - driver-class: org.sqlite.JDBC - username: - password: +neutrino: + proxy: + protocol: + max-frame-length: 2097152 + length-field-offset: 0 + length-field-length: 4 + initial-bytes-to-strip: 0 + length-adjustment: 0 + read-idle-time: 60 + write-idle-time: 40 + all-idle-time-seconds: 0 + tunnel: + boss-thread-count: 2 + work-thread-count: 10 + # 服务端端口,用于保持与客户端的连接,非SSL + port: ${OPEN_PORT:9000} + # 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置 + ssl-port: ${SSL_PORT:9002} + # 证书配置,用于隧道通信SSL加密 + key-store-password: ${STORE_PASS:123456} + key-manager-password: ${MGR_PASS:123456} + jks-path: ${JKS_PATH:classpath:/test.jks} + server: + boss-thread-count: 5 + work-thread-count: 20 + # HTTP代理端口,默认80,也可以用其他端口,走nginx转发 + http-proxy-port: ${HTTP_PROXY_PORT:80} + # HTTPS代理端口,默认443,也可以用其他端口,走nginx转发 + https-proxy-port: ${HTTPS_PROXY_PORT:443} + # 如果不配置,则不支持域名映射 + domain-name: ${DOMAIN_NAME:} + # 证书配置,用于支持HTTPS + key-store-password: ${HTTPS_STORE_PASS:} + jks-path: ${HTTPS_JKS_PATH:} + data: + # 数据库配置(支持mysql) + type: sqlite + url: jdbc:sqlite:data.db + driver-class: org.sqlite.JDBC + username: + password: ``` ## 5.3、启动服务端 diff --git a/neutrino-proxy-client/src/main/resources/logback.xml b/neutrino-proxy-client/src/main/resources/logback.xml index 22d04377..e7178e77 100644 --- a/neutrino-proxy-client/src/main/resources/logback.xml +++ b/neutrino-proxy-client/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DBInitialize.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DBInitialize.java index ea8823e9..7d8ac075 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DBInitialize.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/db/DBInitialize.java @@ -64,8 +64,7 @@ public class DBInitialize implements EventListener { @Override public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable { - // TODO 该事件有50%的概率不触发 - System.out.println("11"); + } /** diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java index 9738ac57..0ec22f42 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfig.java @@ -18,10 +18,15 @@ public class ProxyConfig { @Inject("${neutrino.proxy.protocol}") private Protocol protocol; /** - * 服务端配置 + * 代理服务配置 */ @Inject("${neutrino.proxy.server}") private Server server; + /** + * 代理隧道配置 + */ + @Inject("${neutrino.proxy.tunnel}") + private Tunnel tunnel; @Data public static class Protocol { @@ -37,15 +42,24 @@ public class ProxyConfig { @Data 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 sslPort; private String keyStorePassword; private String keyManagerPassword; private String jksPath; - private Integer bossThreadCount; - private Integer workThreadCount; - private String domainName; - private Integer httpProxyPort; } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java index 41eeebc5..a4c2ca6b 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java @@ -43,4 +43,14 @@ public class ProxyConfiguration implements LifecycleBean { 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()); + } + } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java index bb0f5668..fbbe4e05 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java @@ -2,28 +2,17 @@ package org.dromara.neutrinoproxy.server.proxy.core; import cn.hutool.core.util.StrUtil; import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; 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.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.Inject; import org.noear.solon.core.event.AppLoadEndEvent; import org.noear.solon.core.event.EventListener; -import java.net.InetSocketAddress; - /** * @author: aoshiguchen * @date: 2023/4/2 @@ -54,7 +43,7 @@ public class HttpProxy implements EventListener { @Override public void initChannel(SocketChannel ch) throws Exception { 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(); @@ -63,142 +52,4 @@ public class HttpProxy implements EventListener { log.error("http proxy start err!", e); } } - - private class VisitorChannelHandler extends SimpleChannelInboundHandler { - - @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; - } - } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpVisitorChannelHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpVisitorChannelHandler.java new file mode 100644 index 00000000..adf00073 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpVisitorChannelHandler.java @@ -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 { + /** + * 域名 + */ + 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; + } +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpsProxy.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpsProxy.java new file mode 100644 index 00000000..bfac3abb --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpsProxy.java @@ -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 { + @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() { + @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; + } +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyServerRunner.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyServerRunner.java index 44d59a90..0e634d0e 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyServerRunner.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/ProxyServerRunner.java @@ -36,20 +36,10 @@ import java.security.KeyStore; public class ProxyServerRunner implements EventListener { @Inject private ProxyConfig proxyConfig; - @Inject("serverBossGroup") + @Inject("tunnelBossGroup") private NioEventLoopGroup serverBossGroup; - @Inject("serverWorkerGroup") + @Inject("tunnelWorkerGroup") 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 public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable { startProxyServer(); @@ -68,15 +58,15 @@ public class ProxyServerRunner implements EventListener { } }); try { - bootstrap.bind(port).sync(); - log.info("代理服务启动,端口:{}", port); + bootstrap.bind(proxyConfig.getTunnel().getPort()).sync(); + log.info("代理服务启动,端口:{}", proxyConfig.getTunnel().getPort()); } catch (Exception e) { log.error("代理服务异常", e); } } private void startProxyServerForSSL() { - if (null == sslPort) { + if (null == proxyConfig.getTunnel().getSslPort()) { return; } ServerBootstrap bootstrap = new ServerBootstrap(); @@ -89,8 +79,8 @@ public class ProxyServerRunner implements EventListener { } }); try { - bootstrap.bind(sslPort).sync(); - log.info("代理服务启动,SSL端口: {}", sslPort); + bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync(); + log.info("代理服务启动,SSL端口: {}", proxyConfig.getTunnel().getSslPort()); } catch (Exception e) { log.error("代理服务异常", e); } @@ -98,13 +88,13 @@ public class ProxyServerRunner implements EventListener { private ChannelHandler createSslHandler() { try { - InputStream jksInputStream = FileUtil.getInputStream(jksPath); + InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath()); SSLContext serverContext = SSLContext.getInstance("TLS"); 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()); - kmf.init(ks, keyManagerPassword.toCharArray()); + kmf.init(ks, proxyConfig.getTunnel().getKeyManagerPassword().toCharArray()); TrustManager[] trustManagers = null; serverContext.init(kmf.getKeyManagers(), trustManagers, null); diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/VisitorChannelHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java similarity index 97% rename from neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/VisitorChannelHandler.java rename to neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java index 87648f83..0c52037c 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/VisitorChannelHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/TcpVisitorChannelHandler.java @@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; 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; @@ -23,7 +22,7 @@ import java.net.InetSocketAddress; * @date: 2022/6/16 */ @Slf4j -public class VisitorChannelHandler extends SimpleChannelInboundHandler { +public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/ProtocalService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/ProtocalService.java index cf966e1e..c9552190 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/ProtocalService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/ProtocalService.java @@ -2,6 +2,7 @@ package org.dromara.neutrinoproxy.server.service; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum; import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes; import org.noear.solon.annotation.Component; @@ -22,7 +23,7 @@ public class ProtocalService { public List list() { return Lists.newArrayList( 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("暂不支持") ); } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java index 07324151..6eb7c5aa 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/VisitorChannelService.java @@ -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.UserDO; 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.ProxyMapping; import org.dromara.neutrinoproxy.server.util.ProxyUtil; @@ -224,7 +224,7 @@ public class VisitorChannelService { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addFirst(new BytesMetricsHandler()); - ch.pipeline().addLast(new VisitorChannelHandler()); + ch.pipeline().addLast(new TcpVisitorChannelHandler()); } }); diff --git a/neutrino-proxy-server/src/main/resources/app.yml b/neutrino-proxy-server/src/main/resources/app.yml index 6ff69f89..76a68f1d 100644 --- a/neutrino-proxy-server/src/main/resources/app.yml +++ b/neutrino-proxy-server/src/main/resources/app.yml @@ -12,17 +12,23 @@ neutrino: read-idle-time: 40 write-idle-time: 5 all-idle-time-seconds: 0 - server: - boss-thread-count: 10 - work-thread-count: 60 + tunnel: + boss-thread-count: 2 + work-thread-count: 10 port: ${OPEN_PORT:9000} ssl-port: ${SSL_PORT:9002} key-store-password: ${STORE_PASS:123456} key-manager-password: ${MGR_PASS:123456} jks-path: ${JKS_PATH:classpath:/test.jks} + server: + boss-thread-count: 5 + work-thread-count: 20 http-proxy-port: ${HTTP_PROXY_PORT:80} + https-proxy-port: ${HTTPS_PROXY_PORT:443} # 如果不配置,则不支持域名映射 domain-name: ${DOMAIN_NAME:} + key-store-password: ${HTTPS_STORE_PASS:} + jks-path: ${HTTPS_JKS_PATH:} data: db: type: ${DB_TYPE:sqlite} diff --git a/neutrino-proxy-server/src/main/resources/logback.xml b/neutrino-proxy-server/src/main/resources/logback.xml index 00b2c992..380f20ed 100644 --- a/neutrino-proxy-server/src/main/resources/logback.xml +++ b/neutrino-proxy-server/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + diff --git a/neutrino-proxy-vuepress/docs/.vuepress/public/img/logo-0.75x.png b/neutrino-proxy-vuepress/docs/.vuepress/public/img/logo-0.75x.png new file mode 100755 index 00000000..de120681 Binary files /dev/null and b/neutrino-proxy-vuepress/docs/.vuepress/public/img/logo-0.75x.png differ diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md index d28d6833..7e7ef65d 100644 --- a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md @@ -7,7 +7,7 @@ article: false ## 1、 部署服务端 ### 1.1、 Docker一键部署 -> 当前最新版本为1.8.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0` 指定版本安装,推荐使用`latest`直接安装最新版。 +> 当前最新版本为1.8.2,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.2` 指定版本安装,推荐使用`latest`直接安装最新版。 #### 使用默认sqlite数据库 ```shell diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/04.域名映射.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/04.域名映射.md index 6bd2ab06..e88e3b98 100644 --- a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/04.域名映射.md +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/04.域名映射.md @@ -35,3 +35,28 @@ permalink: /pages/269a2e/ # 所有的代理映射都能绑定域名吗? 不行的。之所以能实现域名绑定,原因是HTTP请求附带了Host请求头,包含了请求的域名信息,代理服务端能根据请求的域名做分发。而TCP协议本身并不包含域名,因此无法实现。 所以,目前只有HTTP(涵盖HTTPS)协议能支持。 + +# 代理服务端使用非80端口时,nginx配置示例 +``` +server { + listen 80; + # 此处配不配貌似不影响 + server_name localhost *.neutrino-proxy.asgc.fun; + + location / { + if ($http_host ~* "(.*?).neutrino-proxy.asgc.fun") { + # 转发到代理服务端HTTP代理端口 + proxy_pass http://localhost:8899; + } + + tcp_nodelay on; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + #以上三行,目的是将代理服务器收到的用户的信息传到真实服务器上 + + root html; + index index.html index.htm; + } +} +``` diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/05.HTTPS配置.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/05.HTTPS配置.md new file mode 100644 index 00000000..e5b0cdab --- /dev/null +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/05.HTTPS配置.md @@ -0,0 +1,25 @@ +--- +title: HTTPS配置 +date: 2023-05-27 11:40:12 +permalink: /pages/99a300/ +--- + +## 基础说明 +- 在中微子代理中,HTTP的定位是:为ip+端口的访问形式增加域名访问的支持。因此: + - 如果端口映射正确配置了域名,那么该映射同时支持IP+端口、域名2种访问形式。 + - 如果端口映射未配置域名,那么只能以ip+端口的形式访问,此时等价于选择TCP协议。 + +- 在中微子代理中,HTTPS作为HTTP的一种增强行为,不作为一个单独的协议。因此: + - 在端口映射中只需要选择HTTP即可 + - 若端口映射未正确配置域名,那么同理,此时等价于选择TCP协议。 + - 若端口映射配置了域名,但是未正确配置HTTPS端口、证书,那么此时可以通过域名访问,但不支持HTTPS + - 若端口映射配置了域名,且正确配置了HTTPS端口、证书,那么此时可以通过IP+端口、http域名、https域名3种形式访问 + + +## HTTPS配置流程 +- 按照域名映射流程,完成域名的配置 +- 在服务端配置域名的HTTPS证书信息(此处应该是通配符证书) + +## HTTPS端口问题 +- 与HTTP类似,HTTPS也有一个默认端口:443,默认情况下需要占用服务端443端口 +- 如果代理服务端不能独占443端口,此处可以指定任意端口,但需要在nginx中配置443端口的转发规则。此处与域名映射中代理服务端不使用80端口的情况类似 diff --git a/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md new file mode 100644 index 00000000..5f4c249b --- /dev/null +++ b/neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/06.服务端配置.md @@ -0,0 +1,59 @@ +--- +title: 服务端配置 +date: 2023-05-27 11:41:44 +permalink: /pages/f2d0f1/ +--- + +::: tip + +1.8.2版本,服务端配置格式有所调整。从1.8.2之前的版本升级到1.8.2之后的版本,需要注意 + +::: + + +# 以下是最新的服务端配置格式(app.yml) +```yml +server: + # 服务端web端口,用于支持HTTP接口,管理后台页面访问 + port: ${WEB_PORT:8888} + +neutrino: + proxy: + # 隧道相关配置-用于维持服务端与客户端的通信 + tunnel: + # 线程池相关配置,用于技术调优,可忽略 + boss-thread-count: 2 + work-thread-count: 10 + # 隧道非SSL端口 + port: ${OPEN_PORT:9000} + # 隧道SSL端口 + ssl-port: ${SSL_PORT:9002} + # 隧道SSL证书配置 + key-store-password: ${STORE_PASS:123456} + key-manager-password: ${MGR_PASS:123456} + jks-path: ${JKS_PATH:classpath:/test.jks} + # 代理服务相关配置 + server: + # 线程池相关配置,用于技术调优,可忽略 + boss-thread-count: 5 + work-thread-count: 20 + # http代理端口,默认80 + http-proxy-port: ${HTTP_PROXY_PORT:80} + # https代理端口,默认443 (需要配置域名、证书) + https-proxy-port: ${HTTPS_PROXY_PORT:443} + # 如果不配置,则不支持域名映射 + domain-name: ${DOMAIN_NAME:} + # https证书配置 + key-store-password: ${HTTPS_STORE_PASS:} + jks-path: ${HTTPS_JKS_PATH:} + data: + db: + # 数据库类型,目前支持sqlite、mysql两种 + type: ${DB_TYPE:sqlite} + # 数据库连接URL + url: ${DB_URL:jdbc:sqlite:data.db} + # 数据库用户名 + username: ${DB_USER:} + # 数据库密码 + password: ${DB_PASSWORD:} +``` diff --git a/neutrino-proxy-vuepress/docs/index.md b/neutrino-proxy-vuepress/docs/index.md index a3d33842..341e7b0b 100644 --- a/neutrino-proxy-vuepress/docs/index.md +++ b/neutrino-proxy-vuepress/docs/index.md @@ -1,7 +1,7 @@ --- home: true -heroImage: /img/logo.png +heroImage: /img/logo-0.75x.png heroText: Neutrino-Proxy tagline: 🚀一个基于Netty的开源内网穿透神器 actionText: 开始使用 → @@ -21,8 +21,12 @@ features: # 可选的 details: 新增、编辑、删除、禁用实时生效。 - title: Docker details: 服务端支持Docker一键部署。 -# - title: SSL证书 -# details: 支持SSL,保护您的信息安全。 + - title: 隧道SSL加密 + details: 隧道通信支持SSL,保护您的数据安全 + - title: HTTPS + details: 支持HTTPS + - title: 多客户端支持 + details: 暂未支持 # 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表 postList: none diff --git a/scripts/unix/client_start.sh b/scripts/unix/client_start.sh index 9ccb4682..81ef6f56 100755 --- a/scripts/unix/client_start.sh +++ b/scripts/unix/client_start.sh @@ -37,7 +37,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out fi rm -f $OUT 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" sleep 15 tail -200 $OUT diff --git a/scripts/unix/server_start.sh b/scripts/unix/server_start.sh index bae06ed3..07ad4fcd 100755 --- a/scripts/unix/server_start.sh +++ b/scripts/unix/server_start.sh @@ -29,7 +29,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out fi rm -f $OUT 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" sleep 15 tail -200 $OUT