From 52d9d183713cee68adae101a15311b9c7d6bced7 Mon Sep 17 00:00:00 2001 From: qaq <1937228092@qq.com> Date: Fri, 20 Jun 2025 11:14:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dui=20=E6=B7=BB=E5=8A=A0gost?= =?UTF-8?q?=E9=9D=99=E9=BB=98http=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + go-gost/x/api/api.go | 1 + go-gost/x/api/middleware.go | 46 ++ go-gost/x/listener/tls/listener.go | 44 +- install.sh | 84 +-- .../java/com/admin/common/dto/TunnelDto.java | 4 + .../com/admin/common/dto/TunnelUpdateDto.java | 40 ++ .../java/com/admin/common/dto/UserDto.java | 3 +- .../admin/controller/TunnelController.java | 8 +- .../java/com/admin/service/TunnelService.java | 8 +- .../admin/service/impl/TunnelServiceImpl.java | 156 +++++- .../admin/service/impl/UserServiceImpl.java | 2 +- vue-frontend/src/App.vue | 1 + vue-frontend/src/api/index.js | 1 + vue-frontend/src/views/ChangePassword.vue | 1 - vue-frontend/src/views/Forward.vue | 2 - vue-frontend/src/views/Home.vue | 22 +- vue-frontend/src/views/Index.vue | 5 +- vue-frontend/src/views/Limit.vue | 2 - vue-frontend/src/views/Login.vue | 5 +- vue-frontend/src/views/Tunnel.vue | 511 +++++++++++++----- vue-frontend/src/views/User.vue | 20 +- vue-frontend/src/views/node.vue | 82 +-- 23 files changed, 815 insertions(+), 237 deletions(-) create mode 100644 springboot-backend/src/main/java/com/admin/common/dto/TunnelUpdateDto.java diff --git a/README.md b/README.md index f14cbca..6226a43 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - 可针对 **指定用户的指定隧道进行限速** 设置 - 支持配置 **单向或双向流量计费方式**,灵活适配不同计费模型 - 提供灵活的转发策略配置,适用于多种网络代理场景 +- 内置轻量级 Agent 端 API 服务,默认强制启用自签发 SSL,仅响应已认证请求,静默运行、安全可控 --- # 流量统计算法 @@ -72,6 +73,9 @@ ```bash +ipv6需要面板端支持,同时开启docker的ipv6服务和composer中的ipv6 + + 如果以前安装过需要重新安装 推荐先删除本地上次下载的文件 panel_install.sh diff --git a/go-gost/x/api/api.go b/go-gost/x/api/api.go index 0b38b52..493f3c6 100644 --- a/go-gost/x/api/api.go +++ b/go-gost/x/api/api.go @@ -38,6 +38,7 @@ func Register(r *gin.Engine, opts *Options) { AllowPrivateNetwork: true, })), gin.Recovery(), + GlobalInterceptor(), ) if opts.AccessLog { r.Use(mwLogger()) diff --git a/go-gost/x/api/middleware.go b/go-gost/x/api/middleware.go index 8a49037..6ad2dca 100644 --- a/go-gost/x/api/middleware.go +++ b/go-gost/x/api/middleware.go @@ -2,6 +2,7 @@ package api import ( "net/http" + "strings" "time" "github.com/gin-gonic/gin" @@ -45,3 +46,48 @@ func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc { } } } + +// GlobalInterceptor 全局HTTP请求拦截器 +func GlobalInterceptor() gin.HandlerFunc { + return func(c *gin.Context) { + // 检查认证参数,如果没有认证就静默关闭 + if !hasValidAuth(c) { + // 获取底层连接并直接关闭 + if hijacker, ok := c.Writer.(http.Hijacker); ok { + if conn, _, err := hijacker.Hijack(); err == nil { + conn.Close() // 直接关闭连接,不发送任何数据 + return + } + } + + // 如果无法hijack连接,则中止请求但不返回响应 + c.Abort() + return + } + + c.Next() + + } +} + +// hasValidAuth 检查请求是否包含有效的认证信息 +func hasValidAuth(c *gin.Context) bool { + // 获取Authorization头 + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + return false + } + + // 检查是否是Basic认证 + if !strings.HasPrefix(authHeader, "Basic ") { + return false + } + + // 解析Basic认证 + _, _, ok := c.Request.BasicAuth() + if !ok { + return false + } + + return true +} diff --git a/go-gost/x/listener/tls/listener.go b/go-gost/x/listener/tls/listener.go index a15aaeb..ee43120 100644 --- a/go-gost/x/listener/tls/listener.go +++ b/go-gost/x/listener/tls/listener.go @@ -68,7 +68,7 @@ func (l *tlsListener) Init(md md.Metadata) (err error) { ln = limiter_wrapper.WrapListener(l.options.Service, ln, l.options.TrafficLimiter) ln = climiter.WrapListener(l.options.ConnLimiter, ln) - l.ln = tls.NewListener(ln, l.options.TLSConfig) + l.ln = newSilentTLSListener(ln, l.options.TLSConfig, l.logger) return } @@ -99,3 +99,45 @@ func (l *tlsListener) Addr() net.Addr { func (l *tlsListener) Close() error { return l.ln.Close() } + +// silentTLSListener 是一个自定义的TLS监听器,对非TLS连接静默处理 +type silentTLSListener struct { + net.Listener + config *tls.Config + logger logger.Logger +} + +func newSilentTLSListener(inner net.Listener, config *tls.Config, logger logger.Logger) net.Listener { + return &silentTLSListener{ + Listener: inner, + config: config, + logger: logger, + } +} + +func (l *silentTLSListener) Accept() (net.Conn, error) { + for { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + // 创建TLS连接 + tlsConn := tls.Server(conn, l.config) + + // 设置握手超时 + tlsConn.SetDeadline(time.Now().Add(10 * time.Second)) + + // 尝试TLS握手 + if err := tlsConn.Handshake(); err != nil { + + conn.Close() + continue // 继续接受下一个连接 + } + + // 清除超时设置 + tlsConn.SetDeadline(time.Time{}) + + return tlsConn, nil + } +} diff --git a/install.sh b/install.sh index d2d2d77..c873600 100644 --- a/install.sh +++ b/install.sh @@ -5,15 +5,17 @@ if [[ "$ARCH" != "x86_64" ]]; then echo "❌ 不支持的架构: $ARCH,仅支持 x86_64。" exit 1 fi -# gost下载地址 + +# 下载地址 DOWNLOAD_URL="https://github.com/bqlpfy/forward-panel/releases/download/gost/gost" +# 解析参数 while getopts "a:p:s:" opt; do case $opt in a) SERVER_ADDR="$OPTARG" ;; p) PORT="$OPTARG" ;; s) SECRET="$OPTARG" ;; - *) echo "无效参数"; exit 1 ;; + *) echo "❌ 无效参数"; exit 1 ;; esac done @@ -22,46 +24,60 @@ if [[ -z "$SERVER_ADDR" || -z "$PORT" || -z "$SECRET" ]]; then exit 1 fi - INSTALL_DIR="/etc/gost" mkdir -p "$INSTALL_DIR" -# 检查gost服务是否存在并运行 +# 停止并禁用已有服务 if systemctl list-units --full -all | grep -Fq "gost.service"; then - echo "检测到已存在的gost服务" - if systemctl is-active --quiet gost; then - echo "停止运行中的gost服务..." - systemctl stop gost - fi - if systemctl is-enabled --quiet gost; then - echo "禁用gost服务自启动..." - systemctl disable gost - fi + echo "🔍 检测到已存在的gost服务" + systemctl stop gost 2>/dev/null && echo "🛑 停止服务" + systemctl disable gost 2>/dev/null && echo "🚫 禁用自启" fi # 删除旧文件 -for FILE in gost config.json gost.json; do - if [[ -f "$INSTALL_DIR/$FILE" ]]; then - echo "删除已有文件: $INSTALL_DIR/$FILE" - rm -f "$INSTALL_DIR/$FILE" - fi -done +[[ -f "$INSTALL_DIR/gost" ]] && echo "🧹 删除旧文件 gost" && rm -f "$INSTALL_DIR/gost" - -# 下载gost可执行文件 +# 下载 gost +echo "⬇️ 下载 gost 中..." curl -L "$DOWNLOAD_URL" -o "$INSTALL_DIR/gost" +if [[ ! -f "$INSTALL_DIR/gost" || ! -s "$INSTALL_DIR/gost" ]]; then + echo "❌ 下载失败,请检查网络或下载链接。" + exit 1 +fi chmod +x "$INSTALL_DIR/gost" +echo "✅ 下载完成" +# 打印版本 +echo "🔎 gost 版本:$($INSTALL_DIR/gost -V)" -cat > "$INSTALL_DIR/config.json" < "$CONFIG_FILE" < "$INSTALL_DIR/gost.json" < "$GOST_CONFIG" < "$INSTALL_DIR/gost.json" < /etc/systemd/system/gost.service < "$SERVICE_FILE" < { */ R getAllTunnels(); - + /** + * 更新隧道(只允许修改名称、流量计费、端口范围) + * @param tunnelUpdateDto 更新数据 + * @return 结果 + */ + R updateTunnel(TunnelUpdateDto tunnelUpdateDto); /** * 删除隧道 diff --git a/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java index 0e9610a..ab714b2 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/TunnelServiceImpl.java @@ -3,6 +3,7 @@ package com.admin.service.impl; import cn.hutool.core.util.StrUtil; import com.admin.common.dto.TunnelDto; import com.admin.common.dto.TunnelListDto; +import com.admin.common.dto.TunnelUpdateDto; import com.admin.common.lang.R; import com.admin.common.utils.JwtUtil; @@ -110,22 +111,30 @@ public class TunnelServiceImpl extends ServiceImpl impleme return nameValidationResult; } - // 2. 验证入口节点和端口 + // 2. 验证隧道转发类型的必要参数 + if (tunnelDto.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { + R tunnelForwardValidationResult = validateTunnelForwardCreate(tunnelDto); + if (tunnelForwardValidationResult.getCode() != 0) { + return tunnelForwardValidationResult; + } + } + + // 3. 验证入口节点和端口 NodeValidationResult inNodeValidation = validateInNode(tunnelDto); if (inNodeValidation.isHasError()) { return R.err(inNodeValidation.getErrorMessage()); } - // 3. 构建隧道实体 + // 4. 构建隧道实体 Tunnel tunnel = buildTunnelEntity(tunnelDto, inNodeValidation.getNode()); - // 4. 根据隧道类型设置出口参数 + // 5. 根据隧道类型设置出口参数 R outNodeSetupResult = setupOutNodeParameters(tunnel, tunnelDto); if (outNodeSetupResult.getCode() != 0) { return outNodeSetupResult; } - // 5. 设置默认属性并保存 + // 6. 设置默认属性并保存 setDefaultTunnelProperties(tunnel); boolean result = this.save(tunnel); @@ -143,6 +152,60 @@ public class TunnelServiceImpl extends ServiceImpl impleme return R.ok(tunnelList); } + /** + * 更新隧道(只允许修改名称、流量计费、端口范围) + * + * @param tunnelUpdateDto 更新数据传输对象 + * @return 更新结果响应 + */ + @Override + public R updateTunnel(TunnelUpdateDto tunnelUpdateDto) { + // 1. 验证隧道是否存在 + Tunnel existingTunnel = this.getById(tunnelUpdateDto.getId()); + if (existingTunnel == null) { + return R.err(ERROR_TUNNEL_NOT_FOUND); + } + + // 2. 验证隧道名称唯一性(排除自身) + R nameValidationResult = validateTunnelNameUniquenessForUpdate( + tunnelUpdateDto.getName(), tunnelUpdateDto.getId()); + if (nameValidationResult.getCode() != 0) { + return nameValidationResult; + } + + // 3. 验证端口范围 + R portValidationResult = validatePortRanges(tunnelUpdateDto); + if (portValidationResult.getCode() != 0) { + return portValidationResult; + } + + // 4. 验证隧道转发类型的出口端口 + if (existingTunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { + R tunnelTypeValidationResult = validateTunnelForwardUpdate(tunnelUpdateDto); + if (tunnelTypeValidationResult.getCode() != 0) { + return tunnelTypeValidationResult; + } + } + + // 5. 更新允许修改的字段 + existingTunnel.setName(tunnelUpdateDto.getName()); + existingTunnel.setFlow(tunnelUpdateDto.getFlow()); + existingTunnel.setInPortSta(tunnelUpdateDto.getInPortSta()); + existingTunnel.setInPortEnd(tunnelUpdateDto.getInPortEnd()); + + if (existingTunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) { + existingTunnel.setOutIpSta(tunnelUpdateDto.getOutIpSta()); + existingTunnel.setOutIpEnd(tunnelUpdateDto.getOutIpEnd()); + }else{ + existingTunnel.setOutIpSta(tunnelUpdateDto.getInPortSta()); + existingTunnel.setOutIpEnd(tunnelUpdateDto.getInPortEnd()); + } + + // 6. 保存更新 + boolean result = this.updateById(existingTunnel); + return result ? R.ok("隧道更新成功") : R.err("隧道更新失败"); + } + /** * 删除隧道 * 删除前会检查是否有转发或用户权限在使用该隧道 @@ -213,6 +276,91 @@ public class TunnelServiceImpl extends ServiceImpl impleme return R.ok(); } + /** + * 验证隧道名称唯一性(更新时使用,排除自身) + * + * @param tunnelName 隧道名称 + * @param tunnelId 隧道ID(要排除的隧道) + * @return 验证结果响应 + */ + private R validateTunnelNameUniquenessForUpdate(String tunnelName, Long tunnelId) { + QueryWrapper query = new QueryWrapper<>(); + query.eq("name", tunnelName); + query.ne("id", tunnelId); // 排除自身 + Tunnel existTunnel = this.getOne(query); + if (existTunnel != null) { + return R.err(ERROR_TUNNEL_NAME_EXISTS); + } + return R.ok(); + } + + /** + * 验证端口范围 + * + * @param tunnelUpdateDto 更新数据传输对象 + * @return 验证结果响应 + */ + private R validatePortRanges(TunnelUpdateDto tunnelUpdateDto) { + // 验证入口端口范围 + if (tunnelUpdateDto.getInPortSta() > tunnelUpdateDto.getInPortEnd()) { + return R.err(ERROR_IN_PORT_RANGE_INVALID); + } + + // 验证出口端口范围(如果有的话) + if (tunnelUpdateDto.getOutIpSta() != null && tunnelUpdateDto.getOutIpEnd() != null) { + if (tunnelUpdateDto.getOutIpSta() > tunnelUpdateDto.getOutIpEnd()) { + return R.err(ERROR_OUT_PORT_RANGE_INVALID); + } + } + + return R.ok(); + } + + /** + * 验证隧道转发更新时的出口端口 + * + * @param tunnelUpdateDto 更新数据传输对象 + * @return 验证结果响应 + */ + private R validateTunnelForwardUpdate(TunnelUpdateDto tunnelUpdateDto) { + // 验证出口端口参数不能为空 + if (tunnelUpdateDto.getOutIpSta() == null || tunnelUpdateDto.getOutIpEnd() == null) { + return R.err(ERROR_OUT_PORT_REQUIRED); + } + + // 验证出口端口范围 + if (tunnelUpdateDto.getOutIpSta() > tunnelUpdateDto.getOutIpEnd()) { + return R.err(ERROR_OUT_PORT_RANGE_INVALID); + } + + return R.ok(); + } + + /** + * 验证隧道转发创建时的必要参数 + * + * @param tunnelDto 隧道创建数据传输对象 + * @return 验证结果响应 + */ + private R validateTunnelForwardCreate(TunnelDto tunnelDto) { + // 验证出口节点不能为空 + if (tunnelDto.getOutNodeId() == null) { + return R.err(ERROR_OUT_NODE_REQUIRED); + } + + // 验证出口端口参数不能为空 + if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) { + return R.err(ERROR_OUT_PORT_REQUIRED); + } + + // 验证出口端口范围 + if (tunnelDto.getOutIpSta() > tunnelDto.getOutIpEnd()) { + return R.err(ERROR_OUT_PORT_RANGE_INVALID); + } + + return R.ok(); + } + /** * 验证入口节点和端口 * diff --git a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java index 682f4ae..eda5e3b 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/UserServiceImpl.java @@ -151,7 +151,7 @@ public class UserServiceImpl extends ServiceImpl implements Us return R.ok(MapUtil.builder() .put(LOGIN_TOKEN_FIELD, token) - .put(LOGIN_NAME_FIELD, user.getName()) + .put(LOGIN_NAME_FIELD, user.getUser()) .put(LOGIN_ROLE_ID_FIELD, user.getRoleId()) .put(LOGIN_REQUIRE_PASSWORD_CHANGE_FIELD, requirePasswordChange) .build()); diff --git a/vue-frontend/src/App.vue b/vue-frontend/src/App.vue index e692b75..4411aa0 100644 --- a/vue-frontend/src/App.vue +++ b/vue-frontend/src/App.vue @@ -41,6 +41,7 @@ html, body { width: 100%; height: 100%; overflow-x: hidden; + overflow-y: hidden; -webkit-text-size-adjust: 100%; } diff --git a/vue-frontend/src/api/index.js b/vue-frontend/src/api/index.js index 775c25d..bf82b38 100644 --- a/vue-frontend/src/api/index.js +++ b/vue-frontend/src/api/index.js @@ -20,6 +20,7 @@ export const getNodeInstallCommand = (id) => Network.post("/node/install", { id export const createTunnel = (data) => Network.post("/tunnel/create", data) export const getTunnelList = () => Network.post("/tunnel/list") export const getTunnelById = (id) => Network.post("/tunnel/get", { id }) +export const updateTunnel = (data) => Network.post("/tunnel/update", data) export const deleteTunnel = (id) => Network.post("/tunnel/delete", { id }) // 用户隧道权限管理操作 - 全部使用POST请求 diff --git a/vue-frontend/src/views/ChangePassword.vue b/vue-frontend/src/views/ChangePassword.vue index 278be04..a0eab41 100644 --- a/vue-frontend/src/views/ChangePassword.vue +++ b/vue-frontend/src/views/ChangePassword.vue @@ -95,7 +95,6 @@ diff --git a/vue-frontend/src/views/Forward.vue b/vue-frontend/src/views/Forward.vue index aabd4cc..2e017f7 100644 --- a/vue-frontend/src/views/Forward.vue +++ b/vue-frontend/src/views/Forward.vue @@ -720,8 +720,6 @@ export default {