diff --git a/neutrino-proxy-admin/src/api/domain.js b/neutrino-proxy-admin/src/api/domain.js new file mode 100644 index 00000000..d3a9f7fd --- /dev/null +++ b/neutrino-proxy-admin/src/api/domain.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function domainNameBindInfo(query) { + return request({ + url: '/domain-name/bind-info', + method: 'get', + params: query + }) +} diff --git a/neutrino-proxy-admin/src/api/protocal.js b/neutrino-proxy-admin/src/api/protocal.js new file mode 100644 index 00000000..9ea90d86 --- /dev/null +++ b/neutrino-proxy-admin/src/api/protocal.js @@ -0,0 +1,17 @@ +import request from '@/utils/request' + +export function fetchList(query) { + return request({ + url: '/protocal/page', + method: 'get', + params: query + }) +} + +export function protocalList(query) { + return request({ + url: '/protocal/list', + method: 'get', + params: query + }) +} diff --git a/neutrino-proxy-admin/src/lang/zh.js b/neutrino-proxy-admin/src/lang/zh.js index 76286040..d0694208 100644 --- a/neutrino-proxy-admin/src/lang/zh.js +++ b/neutrino-proxy-admin/src/lang/zh.js @@ -49,6 +49,7 @@ export default { system: '系统管理', portPool: '端口池管理', portGroup: '端口分组管理', + protocal: '协议管理', proxy: '代理配置', license: 'License管理', portMapping: '端口映射', @@ -162,7 +163,10 @@ export default { resetKey: '重置Key', upFlow: '上行流量', downFlow: '下行流量', - totalFlow: '总流量' + totalFlow: '总流量', + protocalName: '协议', + supportStatus: '支持状态', + domainName: '域名' }, button: { lookOver: '查看' diff --git a/neutrino-proxy-admin/src/router/index.js b/neutrino-proxy-admin/src/router/index.js index 8fc01e28..3c246651 100644 --- a/neutrino-proxy-admin/src/router/index.js +++ b/neutrino-proxy-admin/src/router/index.js @@ -77,6 +77,7 @@ export const asyncRouterMap = [ { path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }}, { path: 'portGroup', component: _import('system/portGroup'), name: 'portGroup', meta: { title: 'portGroup' }}, { path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }}, + { path: 'protocal', component: _import('system/protocal'), name: 'protocal', meta: { title: 'protocal' }}, { path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }} ] }, diff --git a/neutrino-proxy-admin/src/views/proxy/portMapping.vue b/neutrino-proxy-admin/src/views/proxy/portMapping.vue index 4336c7fd..9b459979 100644 --- a/neutrino-proxy-admin/src/views/proxy/portMapping.vue +++ b/neutrino-proxy-admin/src/views/proxy/portMapping.vue @@ -7,6 +7,9 @@ + + + @@ -33,17 +36,27 @@ {{ scope.row.userName }} - + - + + + + + + + - + @@ -113,6 +126,12 @@ style="width: 280px" />--> + + + + + + @@ -125,6 +144,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 d396ca42..89ec0d2e 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 @@ -44,6 +44,7 @@ public class ProxyConfig { private String jksPath; private Integer bossThreadCount; private Integer workThreadCount; + private String domainName; } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/ExceptionConstant.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/ExceptionConstant.java index 77ea5016..6ed4c3e9 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/ExceptionConstant.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/ExceptionConstant.java @@ -55,6 +55,9 @@ public enum ExceptionConstant { // 端口映射管理(14000) PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"), PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"), + AN_UNSUPPORTED_PROTOCOL(14002, "不支持的协议[{}]!"), + PORT_MAPPING_SUBDONAME_CONNOT_REPEAT(14003, "子域名不能重复使用!"), + // 调度管理(15000) JOB_INFO_NOT_EXIST(15000, "调度管理记录不存在"), SYSTEM_ERROR(500, "系统异常"), diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java new file mode 100644 index 00000000..25b4677b --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/constant/NetworkProtocolEnum.java @@ -0,0 +1,28 @@ +package org.dromara.neutrinoproxy.server.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author: aoshiguchen + * @date: 2023/4/2 + */ +@Getter +@AllArgsConstructor +public enum NetworkProtocolEnum { + TCP("TCP"), + UDP("UDP"), + HTTP("HTTP"), + ; + private String desc; + private static final Map map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity())); + + public static NetworkProtocolEnum of(String desc) { + return map.get(desc); + } +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/DomainNameController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/DomainNameController.java new file mode 100644 index 00000000..3ff09d03 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/DomainNameController.java @@ -0,0 +1,27 @@ +package org.dromara.neutrinoproxy.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Inject; +import org.noear.solon.annotation.Mapping; + +/** + * @author: aoshiguchen + * @date: 2023/4/2 + */ +@Slf4j +@Mapping("/domain-name") +@Controller +public class DomainNameController { + @Inject + private ProxyConfig proxyConfig; + + @Get + @Mapping("/bind-info") + public String bindInfo () { + return proxyConfig.getServer().getDomainName(); + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java index edfc2ba9..061f6d53 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/PortMappingController.java @@ -2,6 +2,8 @@ package org.dromara.neutrinoproxy.server.controller; import org.dromara.neutrinoproxy.server.base.page.PageInfo; import org.dromara.neutrinoproxy.server.base.page.PageQuery; +import org.dromara.neutrinoproxy.server.constant.ExceptionConstant; +import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum; import org.dromara.neutrinoproxy.server.controller.req.proxy.*; import org.dromara.neutrinoproxy.server.controller.res.proxy.*; import org.dromara.neutrinoproxy.server.controller.req.proxy.*; @@ -37,10 +39,17 @@ public class PortMappingController { ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId"); ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort"); ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort"); + ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal"); if (StringUtils.isBlank(req.getClientIp())) { // 没传客户端ip,默认为127.0.0.1 req.setClientIp("127.0.0.1"); } + NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal()); + ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal()); + if (networkProtocolEnum != NetworkProtocolEnum.HTTP) { + // 目前仅HTTP支持绑定域名 + req.setSubdomain(""); + } return portMappingService.create(req); } @@ -49,6 +58,20 @@ public class PortMappingController { @Mapping("/update") public PortMappingUpdateRes update(PortMappingUpdateReq req) { ParamCheckUtil.checkNotNull(req, "req"); + ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId"); + ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort"); + ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort"); + ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal"); + if (StringUtils.isBlank(req.getClientIp())) { + // 没传客户端ip,默认为127.0.0.1 + req.setClientIp("127.0.0.1"); + } + NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal()); + ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal()); + if (networkProtocolEnum != NetworkProtocolEnum.HTTP) { + // 目前仅HTTP支持绑定域名 + req.setSubdomain(""); + } return portMappingService.update(req); } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/ProtocalController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/ProtocalController.java new file mode 100644 index 00000000..2385fac4 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/ProtocalController.java @@ -0,0 +1,40 @@ +package org.dromara.neutrinoproxy.server.controller; + +import org.dromara.neutrinoproxy.server.base.page.PageInfo; +import org.dromara.neutrinoproxy.server.base.page.PageQuery; +import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes; +import org.dromara.neutrinoproxy.server.service.ProtocalService; +import org.dromara.neutrinoproxy.server.util.ParamCheckUtil; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Inject; +import org.noear.solon.annotation.Mapping; + +import java.util.List; + +/** + * @author: aoshiguchen + * @date: 2023/4/2 + */ +@Mapping("/protocal") +@Controller +public class ProtocalController { + + @Inject + private ProtocalService protocalService; + + @Get + @Mapping("/page") + public PageInfo page(PageQuery pageQuery) { + ParamCheckUtil.checkNotNull(pageQuery, "pageQuery"); + + List list = protocalService.list(); + return PageInfo.of(protocalService.list(), (long)list.size(), pageQuery.getCurrent(), pageQuery.getSize()); + } + + @Get + @Mapping("/list") + public List list() { + return protocalService.list(); + } +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java index 3fddf54d..ecaa3a7d 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingCreateReq.java @@ -34,6 +34,14 @@ public class PortMappingCreateReq { * licenseId */ private Integer licenseId; + /** + * 协议 + */ + private String protocal; + /** + * 子域名 + */ + private String subdomain; /** * 服务端端口 */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingListReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingListReq.java index d323fe56..d8989abe 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingListReq.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingListReq.java @@ -36,6 +36,10 @@ public class PortMappingListReq { * 用户ID */ private Integer userId; + /** + * 协议 + */ + private String protocal; /** * licenseId */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java index eb64f71c..5e9d86c5 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/req/proxy/PortMappingUpdateReq.java @@ -38,6 +38,14 @@ public class PortMappingUpdateReq { * licenseId */ private Integer licenseId; + /** + * 协议 + */ + private String protocal; + /** + * 子域名 + */ + private String subdomain; /** * 服务端端口 */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java index 55d7db5b..f0c6e3e4 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/proxy/PortMappingListRes.java @@ -39,6 +39,18 @@ public class PortMappingListRes { * licenseId */ private Integer licenseId; + /** + * 协议 + */ + private String protocal; + /** + * 子域名 + */ + private String subdomain; + /** + * 域名 + */ + private String domain; /** * license名称 */ diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/PortPoolListRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/PortPoolListRes.java index bd605af9..1022ad64 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/PortPoolListRes.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/PortPoolListRes.java @@ -22,6 +22,7 @@ package org.dromara.neutrinoproxy.server.controller.res.system; import lombok.Data; +import lombok.experimental.Accessors; import java.util.Date; @@ -30,6 +31,7 @@ import java.util.Date; * @author: aoshiguchen * @date: 2022/8/7 */ +@Accessors(chain = true) @Data public class PortPoolListRes { private Integer id; diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/ProtocalListRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/ProtocalListRes.java new file mode 100644 index 00000000..988bcfc3 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/system/ProtocalListRes.java @@ -0,0 +1,26 @@ +package org.dromara.neutrinoproxy.server.controller.res.system; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 协议列表响应 + * @author: aoshiguchen + * @date: 2023/4/2 + */ +@Accessors(chain = true) +@Data +public class ProtocalListRes { + /** + * 协议名称 + */ + private String name; + /** + * 启用状态 + */ + private Boolean enable; + /** + * 备注 + */ + private String remark; +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/PortMappingMapper.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/PortMappingMapper.java index 77c9bb24..40a4f89f 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/PortMappingMapper.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/PortMappingMapper.java @@ -42,6 +42,18 @@ public interface PortMappingMapper extends BaseMapper { ); } + /** + * 校验子域名是否重复 + * @param subdomain + * @return + */ + default Boolean checkRepeatBySubdomain(String subdomain, Set excludeIds) { + return this.selectCount(new LambdaQueryWrapper() + .eq(PortMappingDO::getSubdomain, subdomain) + .notIn(!CollectionUtil.isEmpty(excludeIds), PortMappingDO::getId, excludeIds) + ).intValue() > 0; + } + default List findEnableListByLicenseId(Integer licenseId) { return this.selectList(new LambdaQueryWrapper() .eq(PortMappingDO::getLicenseId, licenseId) diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java index db1d3d1a..425b04b1 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/dal/entity/PortMappingDO.java @@ -48,6 +48,14 @@ public class PortMappingDO { * licenseId */ private Integer licenseId; + /** + * 协议 + */ + private String protocal; + /** + * 子域名 + */ + private String subdomain; /** * 服务端端口 */ 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 new file mode 100644 index 00000000..ff680fb2 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/HttpProxy.java @@ -0,0 +1,193 @@ +package org.dromara.neutrinoproxy.server.proxy.core; + +import cn.hutool.core.util.StrUtil; +import com.sun.deploy.net.proxy.ProxyType; +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 + */ +@Slf4j +@Component +public class HttpProxy 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())) { + log.info("no config domain name,nonsupport http 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().addFirst(new BytesMetricsHandler()); + ch.pipeline().addLast(new VisitorChannelHandler()); + } + }); + bootstrap.bind("0.0.0.0", 80).sync(); + log.info("Http代理服务启动成功!"); + } catch (Exception e) { + 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; + } + String host = getHost(byteBuf); + 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; + } + + String visitorId = ProxyUtil.newVisitorId(); + Channel visitorChannel = ctx.channel(); + + byte[] bytes = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(bytes); + 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(visitorId, bytes)); + + // 增加流量计数 + VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel); + Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length); + }); + + ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, 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 exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + // 当出现异常就关闭连接 + ctx.close(); + } + + private String getHost(ByteBuf byteBuf) { + byte[] buf = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(buf); + byteBuf.resetReaderIndex(); + 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/VisitorChannelHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/core/VisitorChannelHandler.java index 4f5d12f5..a0f74135 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/VisitorChannelHandler.java @@ -1,8 +1,10 @@ package org.dromara.neutrinoproxy.server.proxy.core; 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; @@ -14,44 +16,42 @@ import io.netty.channel.SimpleChannelInboundHandler; import org.noear.solon.Solon; import java.net.InetSocketAddress; -import java.util.concurrent.atomic.AtomicLong; /** * * @author: aoshiguchen * @date: 2022/6/16 */ +@Slf4j public class VisitorChannelHandler extends SimpleChannelInboundHandler { - private static AtomicLong visitorIdProducer = new AtomicLong(0); - @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - // 当出现异常就关闭连接 ctx.close(); + log.error("VisitorChannel error", cause); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { - // 通知代理客户端 Channel visitorChannel = ctx.channel(); Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get(); - if (proxyChannel == null) { + if (null == proxyChannel) { // 该端口还没有代理客户端 ctx.channel().close(); - } else { - byte[] bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); - String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel); - proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes)); - - // 增加流量计数 - VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel); - Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length); + return; } + // 转发代理数据 + byte[] bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel); + proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes)); + + // 增加流量计数 + VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel); + Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length); } @Override @@ -60,23 +60,26 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress(); Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort()); - if (cmdChannel == null) { + if (null == cmdChannel) { // 该端口还没有代理客户端 ctx.channel().close(); - } else { - String visitorId = newVisitorId(); - String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort()); - if (StrUtil.isEmpty(lanInfo)) { - ctx.channel().close(); - } else { - // 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态 - visitorChannel.config().setOption(ChannelOption.AUTO_READ, false); - - ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort()); - cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes())); - } + return; } + // 根据代理服务端端口,获取被代理客户端局域网连接信息 + String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort()); + if (StrUtil.isEmpty(lanInfo)) { + ctx.channel().close(); + return; + } + + // 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态 + visitorChannel.config().setOption(ChannelOption.AUTO_READ, false); + + String visitorId = ProxyUtil.newVisitorId(); + ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort()); + cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes())); + super.channelActive(ctx); } @@ -84,8 +87,8 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler public void channelInactive(ChannelHandlerContext ctx) throws Exception { // 通知代理客户端 - Channel userChannel = ctx.channel(); - InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress(); + Channel visitorChannel = ctx.channel(); + InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress(); Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort()); if (cmdChannel == null) { @@ -95,10 +98,13 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler } else { // 用户连接断开,从控制连接中移除 - String userId = ProxyUtil.getVisitorIdByChannel(userChannel); - ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, userId); + String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel); + ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId); - Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get(); + // 删除代理附加对象 + 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(); @@ -106,7 +112,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler proxyChannel.config().setOption(ChannelOption.AUTO_READ, true); // 通知客户端,用户连接已经断开 - proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId)); + proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId)); } } @@ -134,12 +140,4 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler super.channelWritabilityChanged(ctx); } - /** - * 为访问者连接产生ID - * - * @return - */ - private static String newVisitorId() { - return String.valueOf(visitorIdProducer.incrementAndGet()); - } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/Metrics.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/Metrics.java index feb87e8d..8606709a 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/Metrics.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/Metrics.java @@ -1,25 +1,3 @@ -/** - * 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.server.proxy.domain; import lombok.Data; diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/ProxyAttachment.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/ProxyAttachment.java new file mode 100644 index 00000000..e2ffa47b --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/domain/ProxyAttachment.java @@ -0,0 +1,29 @@ +package org.dromara.neutrinoproxy.server.proxy.domain; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; + +import java.util.function.BiConsumer; + +/** + * 代理连接附件 + * @author: aoshiguchen + * @date: 2023/4/2 + */ +public class ProxyAttachment { + private Channel channel; + private byte[] bytes; + private BiConsumer executor; + + public ProxyAttachment(Channel channel, byte[] bytes, BiConsumer executor) { + this.channel = channel; + this.bytes = bytes; + this.executor = executor; + } + + public void execute() { + if (null != executor) { + this.executor.accept(channel, bytes); + } + } +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java index 3882960a..4c0ae9b4 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java @@ -7,6 +7,7 @@ import org.dromara.neutrinoproxy.core.dispatcher.Match; import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO; import org.dromara.neutrinoproxy.server.dal.entity.UserDO; +import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment; import org.dromara.neutrinoproxy.server.service.LicenseService; import org.dromara.neutrinoproxy.server.service.UserService; import org.dromara.neutrinoproxy.server.util.ProxyUtil; @@ -74,13 +75,20 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler { } Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, visitorId); - if (visitorChannel != null) { - ctx.channel().attr(Constants.VISITOR_ID).set(visitorId); - ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId()); - ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel); - visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel()); - // 代理客户端与后端服务器连接成功,修改用户连接为可读状态 - visitorChannel.config().setOption(ChannelOption.AUTO_READ, true); + if (null == visitorChannel) { + return; + } + ctx.channel().attr(Constants.VISITOR_ID).set(visitorId); + ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId()); + ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel); + visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel()); + // 代理客户端与后端服务器连接成功,修改用户连接为可读状态 + visitorChannel.config().setOption(ChannelOption.AUTO_READ, true); + + // 获取代理附加对象 + ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId); + if (null != proxyAttachment) { + proxyAttachment.execute(); } } diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java index 8af550f6..443466af 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/PortMappingService.java @@ -1,15 +1,19 @@ package org.dromara.neutrinoproxy.server.service; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.google.common.collect.Sets; import org.dromara.neutrinoproxy.server.base.page.PageInfo; import org.dromara.neutrinoproxy.server.base.page.PageQuery; +import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig; import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder; import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum; import org.dromara.neutrinoproxy.server.constant.ExceptionConstant; +import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum; import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum; import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingCreateReq; import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq; @@ -28,6 +32,7 @@ import org.dromara.neutrinoproxy.server.util.ParamCheckUtil; import ma.glasnost.orika.MapperFacade; import org.apache.ibatis.solon.annotation.Db; import org.dromara.neutrinoproxy.server.controller.res.proxy.*; +import org.dromara.neutrinoproxy.server.util.ProxyUtil; import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Inject; import org.noear.solon.core.Lifecycle; @@ -58,6 +63,8 @@ public class PortMappingService implements Lifecycle { @Inject private PortPoolService portPoolService; + @Inject + private ProxyConfig proxyConfig; public PageInfo page(PageQuery pageQuery, PortMappingListReq req) { Page result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize()); @@ -90,6 +97,9 @@ public class PortMappingService implements Lifecycle { return; } item.setUserName(user.getName()); + if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) { + item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName()); + } }); //sorted [userId asc] [licenseId asc] [createTime asc] respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId) @@ -109,11 +119,13 @@ public class PortMappingService implements Lifecycle { PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort()); ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST); ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort()); - + ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT); Date now = new Date(); PortMappingDO portMappingDO = new PortMappingDO(); portMappingDO.setLicenseId(req.getLicenseId()); + portMappingDO.setProtocal(req.getProtocal()); + portMappingDO.setSubdomain(req.getSubdomain()); portMappingDO.setServerPort(req.getServerPort()); portMappingDO.setClientIp(req.getClientIp()); portMappingDO.setClientPort(req.getClientPort()); @@ -124,6 +136,12 @@ public class PortMappingService implements Lifecycle { portMappingMapper.insert(portMappingDO); // 更新VisitorChannel visitorChannelService.addVisitorChannelByPortMapping(portMappingDO); + // 更新域名映射 + if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) && + StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && + StrUtil.isNotBlank(portMappingDO.getSubdomain())) { + ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort()); + } return new PortMappingCreateRes(); } @@ -137,6 +155,7 @@ public class PortMappingService implements Lifecycle { PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort()); ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST); ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort()); + ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT); // 查询原端口映射 PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId()); @@ -144,6 +163,8 @@ public class PortMappingService implements Lifecycle { PortMappingDO portMappingDO = new PortMappingDO(); portMappingDO.setId(req.getId()); + portMappingDO.setProtocal(req.getProtocal()); + portMappingDO.setSubdomain(req.getSubdomain()); portMappingDO.setLicenseId(req.getLicenseId()); portMappingDO.setServerPort(req.getServerPort()); portMappingDO.setClientIp(req.getClientIp()); @@ -153,6 +174,12 @@ public class PortMappingService implements Lifecycle { portMappingMapper.updateById(portMappingDO); // 更新VisitorChannel visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO); + // 更新域名映射 + if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) && + StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && + StrUtil.isNotBlank(portMappingDO.getSubdomain())) { + ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort()); + } return new PortMappingUpdateRes(); } @@ -222,6 +249,11 @@ public class PortMappingService implements Lifecycle { // 更新VisitorChannel visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO); + // 更新域名映射 + if (NetworkProtocolEnum.HTTP.getDesc().equals(portMappingDO.getProtocal()) && + StrUtil.isNotBlank(portMappingDO.getSubdomain())) { + ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain()); + } } /** @@ -239,6 +271,24 @@ public class PortMappingService implements Lifecycle { @Override public void start() throws Throwable { portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date()); + + // 未配置域名,则不需要处理域名映射逻辑 + if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) { + return; + } + List portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper() + .eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()) + .isNotNull(PortMappingDO::getSubdomain) + ); + if (CollectionUtil.isEmpty(portMappingDOList)) { + return; + } + portMappingDOList.forEach(item -> { + if (StrUtil.isBlank(item.getSubdomain())) { + return; + } + ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort()); + }); } /** 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 new file mode 100644 index 00000000..cf966e1e --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/service/ProtocalService.java @@ -0,0 +1,30 @@ +package org.dromara.neutrinoproxy.server.service; + +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes; +import org.noear.solon.annotation.Component; + +import java.util.List; + +/** + * @author: aoshiguchen + * @date: 2023/4/2 + */ +@Slf4j +@Component +public class ProtocalService { + + /** + * 获取协议列表 + * @return + */ + 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("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持") + ); + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java index 85679290..3aedf2db 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil; import com.google.common.collect.Sets; import org.dromara.neutrinoproxy.core.ChannelAttribute; import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo; +import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment; import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping; import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo; import io.netty.channel.Channel; @@ -12,6 +13,7 @@ import io.netty.util.AttributeKey; import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -47,6 +49,18 @@ public class ProxyUtil { * cmdChannelAttachInfo.getUserChannelMap() 读写锁 */ private static final ReadWriteLock userChannelMapLock = new ReentrantReadWriteLock(); + /** + * 访问者ID生成器 + */ + private static AtomicLong visitorIdProducer = new AtomicLong(0); + /** + * 代理 - connect附加映射 + */ + private static Map proxyConnectAttachmentMap = new HashMap<>(); + /** + * 子域名 - 服务端端口映射 + */ + private static Map subdomainToServerPort = new HashMap<>(); /** * 初始化代理信息 @@ -278,4 +292,65 @@ public class ProxyUtil { } return channel.attr(CHANNEL_ATTR_KEY).get().get("attachInfo"); } + + /** + * 为访问者连接产生ID + * + * @return + */ + public static String newVisitorId() { + return String.valueOf(visitorIdProducer.incrementAndGet()); + } + + /** + * 添加代理附加对象 + * @param visitorId + * @param proxyAttachment + */ + public static void addProxyConnectAttachment(String visitorId, ProxyAttachment proxyAttachment) { + proxyConnectAttachmentMap.put(visitorId, proxyAttachment); + } + + /** + * 获取代理附加对象 + * @param visitorId + * @return + */ + public static ProxyAttachment getProxyConnectAttachment(String visitorId) { + return proxyConnectAttachmentMap.get(visitorId); + } + + /** + * 删除代理附加对象 + * @param visitorId + */ + public static void remoteProxyConnectAttachment(String visitorId) { + proxyConnectAttachmentMap.remove(visitorId); + } + + /** + * 设置子域名到服务端端口的映射 + * @param subdomain + * @param serverPort + */ + public static void setSubdomainToServerPort(String subdomain, Integer serverPort) { + subdomainToServerPort.put(subdomain, serverPort); + } + + /** + * 删除子域名到服务端端口的映射 + * @param subdomain + */ + public static void removeSubdomainToServerPort(String subdomain) { + subdomainToServerPort.remove(subdomain); + } + + /** + * 根据子域名获取外网端口 + * @param subdomain + * @return + */ + public static Integer getServerPortBySubdomain(String subdomain) { + return subdomainToServerPort.get(subdomain); + } } diff --git a/neutrino-proxy-server/src/main/resources/mapper/PortMappingMapper.xml b/neutrino-proxy-server/src/main/resources/mapper/PortMappingMapper.xml index 35e29b88..a9fadaf7 100644 --- a/neutrino-proxy-server/src/main/resources/mapper/PortMappingMapper.xml +++ b/neutrino-proxy-server/src/main/resources/mapper/PortMappingMapper.xml @@ -20,6 +20,9 @@ AND pm.enable = #{req.enable} + + AND pm.protocal = #{req.protocal} + order by pm.id asc diff --git a/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql b/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql index 502be1b6..78f9672c 100644 --- a/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql +++ b/neutrino-proxy-server/src/main/resources/sql/mysql/init-structure.sql @@ -69,6 +69,8 @@ CREATE TABLE IF NOT EXISTS `license` ( CREATE TABLE IF NOT EXISTS `port_mapping` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `license_id` int NOT NULL COMMENT 'licenseID', + `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议', + `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)', `server_port` int NOT NULL COMMENT '服务端端口', `client_ip` varchar(20) NOT NULL COMMENT '客户端IP', `client_port` int NOT NULL COMMENT '客户端端口', diff --git a/neutrino-proxy-server/src/main/resources/sql/mysql/port_mapping.data.sql b/neutrino-proxy-server/src/main/resources/sql/mysql/port_mapping.data.sql index 4ffcdba7..e7858bef 100644 --- a/neutrino-proxy-server/src/main/resources/sql/mysql/port_mapping.data.sql +++ b/neutrino-proxy-server/src/main/resources/sql/mysql/port_mapping.data.sql @@ -1,5 +1,7 @@ #port_mapping +INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES +(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now()); INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES -(1, 1, 9101, '127.0.0.1', 8080, 2, 1, now(), now()); +(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now()); INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES -(2, 1, 9102, '127.0.0.1', 3306, 2, 1, now(), now()); +(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now()); diff --git a/neutrino-proxy-server/src/main/resources/sql/mysql/update/UPDATE-20230402.SQL b/neutrino-proxy-server/src/main/resources/sql/mysql/update/UPDATE-20230402.SQL new file mode 100644 index 00000000..8017105d --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/mysql/update/UPDATE-20230402.SQL @@ -0,0 +1,2 @@ +ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议'; +ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)'; \ No newline at end of file diff --git a/neutrino-proxy-server/src/main/resources/sql/sqlite/init-structure.sql b/neutrino-proxy-server/src/main/resources/sql/sqlite/init-structure.sql index 149617ce..602fbf54 100644 --- a/neutrino-proxy-server/src/main/resources/sql/sqlite/init-structure.sql +++ b/neutrino-proxy-server/src/main/resources/sql/sqlite/init-structure.sql @@ -63,6 +63,8 @@ CREATE UNIQUE INDEX IF NOT EXISTS I_license_key ON `license` (`key` ASC); CREATE TABLE IF NOT EXISTS `port_mapping` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `license_id` INTEGER(20) NOT NULL, + `protocal` VARCHAR(10) NOT NULL DEFAULT 'TCP', + `subdomain` VARCHAR(50) DEFAULT NULL, `server_port` INTEGER NOT NULL, `client_ip` VARCHAR(20) NOT NULL, `client_port` INTEGER NOT NULL, diff --git a/neutrino-proxy-server/src/main/resources/sql/sqlite/port_mapping.data.sql b/neutrino-proxy-server/src/main/resources/sql/sqlite/port_mapping.data.sql index 50256c16..81798272 100644 --- a/neutrino-proxy-server/src/main/resources/sql/sqlite/port_mapping.data.sql +++ b/neutrino-proxy-server/src/main/resources/sql/sqlite/port_mapping.data.sql @@ -1,5 +1,7 @@ #port_mapping +INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES +(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW')); INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES -(1, 1, 9101, '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW')); +(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW')); INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES -(2, 1, 9102, '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW')); +(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW')); \ No newline at end of file diff --git a/neutrino-proxy-server/src/main/resources/sql/sqlite/update/UPDATE-20230402.SQL b/neutrino-proxy-server/src/main/resources/sql/sqlite/update/UPDATE-20230402.SQL new file mode 100644 index 00000000..d7a00fe8 --- /dev/null +++ b/neutrino-proxy-server/src/main/resources/sql/sqlite/update/UPDATE-20230402.SQL @@ -0,0 +1,2 @@ +ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP'; +ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL; \ No newline at end of file