diff --git a/go-gost/go.mod b/go-gost/go.mod index 3c31923..153a546 100644 --- a/go-gost/go.mod +++ b/go-gost/go.mod @@ -5,7 +5,6 @@ go 1.23.0 toolchain go1.23.4 require ( - github.com/apernet/OpenGFW v0.0.0-00010101000000-000000000000 github.com/go-gost/core v0.3.1 github.com/go-gost/x v0.5.3 github.com/judwhite/go-svc v1.2.1 @@ -149,4 +148,3 @@ require ( replace github.com/go-gost/x => ./x -replace github.com/apernet/OpenGFW => ./gfw diff --git a/go-gost/main.go b/go-gost/main.go index 226c346..2ea5be4 100644 --- a/go-gost/main.go +++ b/go-gost/main.go @@ -4,7 +4,6 @@ import ( "context" "flag" "fmt" - //"github.com/apernet/OpenGFW/cmd" "log" _ "net/http/pprof" "os" @@ -120,7 +119,7 @@ func main() { log := xlogger.NewLogger() logger.SetDefault(log) - wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, "1.2.2") + wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, config.Http, config.Tls, config.Socks, "1.2.3") defer wsReporter.Stop() service.SetHTTPReportURL(config.Addr, config.Secret) diff --git a/go-gost/x/service/service.go b/go-gost/x/service/service.go index 1f4cd2a..5c50b20 100644 --- a/go-gost/x/service/service.go +++ b/go-gost/x/service/service.go @@ -52,6 +52,14 @@ var isSocks = 0 var needWrap = false +// SetProtocolBlock sets protocol blocking switches and recomputes wrapper need +func SetProtocolBlock(httpOn int, tlsOn int, socksOn int) { + isHttp = httpOn + isTls = tlsOn + isSocks = socksOn + needWrap = isTls+isSocks+isHttp > 0 +} + type Option func(opts *options) func init() { diff --git a/go-gost/x/socket/websocket_reporter.go b/go-gost/x/socket/websocket_reporter.go index 55f5b06..dc7db66 100644 --- a/go-gost/x/socket/websocket_reporter.go +++ b/go-gost/x/socket/websocket_reporter.go @@ -8,17 +8,20 @@ import ( "fmt" "net" "net/url" + "strconv" "strings" "sync" // 新增:用于管理连接状态的互斥锁 "time" "github.com/go-gost/x/config" + "github.com/go-gost/x/service" "github.com/go-gost/x/internal/util/crypto" "github.com/gorilla/websocket" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/mem" psnet "github.com/shirou/gopsutil/v3/net" + "os" ) // SystemInfo 系统信息结构体 @@ -524,6 +527,11 @@ func (w *WebSocketReporter) routeCommand(cmd CommandMessage) { response.Type = "TcpPingResponse" response.Data = tcpPingResult + // Protocol blocking switches + case "SetProtocol": + err = w.handleSetProtocol(cmd.Data) + response.Type = "SetProtocolResponse" + default: err = fmt.Errorf("未知命令类型: %s", cmd.Type) response.Type = "UnknownCommandResponse" @@ -765,6 +773,85 @@ func (w *WebSocketReporter) handleDeleteLimiter(data interface{}) error { return deleteLimiter(deleteReq) } +// handleSetProtocol 处理设置屏蔽协议的命令 +func (w *WebSocketReporter) handleSetProtocol(data interface{}) error { + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("序列化协议设置失败: %v", err) + } + + // 支持 {"http":0/1, "tls":0/1, "socks":0/1} + var req struct { + HTTP *int `json:"http"` + TLS *int `json:"tls"` + SOCKS *int `json:"socks"` + } + if err := json.Unmarshal(jsonData, &req); err != nil { + return fmt.Errorf("解析协议设置失败: %v", err) + } + + // 读取当前值作为默认 + httpVal, tlsVal, socksVal := 0, 0, 0 + + if req.HTTP != nil { + if *req.HTTP != 0 && *req.HTTP != 1 { + return fmt.Errorf("http 取值必须为0或1") + } + httpVal = *req.HTTP + } + if req.TLS != nil { + if *req.TLS != 0 && *req.TLS != 1 { + return fmt.Errorf("tls 取值必须为0或1") + } + tlsVal = *req.TLS + } + if req.SOCKS != nil { + if *req.SOCKS != 0 && *req.SOCKS != 1 { + return fmt.Errorf("socks 取值必须为0或1") + } + socksVal = *req.SOCKS + } + + // 设置至 service,全量传递(未提供的值沿用0) + service.SetProtocolBlock(httpVal, tlsVal, socksVal) + + // 同步写入本地 config.json + if err := updateLocalConfigJSON(httpVal, tlsVal, socksVal); err != nil { + return fmt.Errorf("写入config.json失败: %v", err) + } + return nil +} + +// updateLocalConfigJSON 将 http/tls/socks 写入工作目录下的 config.json +func updateLocalConfigJSON(httpVal int, tlsVal int, socksVal int) error { + path := "config.json" + + // 读取现有配置 + type LocalConfig struct { + Addr string `json:"addr"` + Secret string `json:"secret"` + Http int `json:"http"` + Tls int `json:"tls"` + Socks int `json:"socks"` + } + + var cfg LocalConfig + if b, err := os.ReadFile(path); err == nil { + _ = json.Unmarshal(b, &cfg) + } + + cfg.Http = httpVal + cfg.Tls = tlsVal + cfg.Socks = socksVal + + // 写回 + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0644) +} + // handleCall 处理服务端的call回调消息 func (w *WebSocketReporter) handleCall(data interface{}) error { // 解析call数据 @@ -931,15 +1018,15 @@ func getMemoryInfo() MemoryInfo { return memInfo } -// StartWebSocketReporterWithConfig 使用配置启动WebSocket报告器 -func StartWebSocketReporterWithConfig(Addr string, Secret string, Version string) *WebSocketReporter { +// StartWebSocketReporterWithConfig 使用配置字段启动WebSocket报告器 +func StartWebSocketReporterWithConfig(addr string, secret string, http int, tls int, socks int, version string) *WebSocketReporter { // 构建包含本机IP的WebSocket URL - var fullURL = "ws://" + Addr + "/system-info?type=1&secret=" + Secret + "&version=" + Version + fullURL := "ws://" + addr + "/system-info?type=1&secret=" + secret + "&version=" + version + "&http=" + strconv.Itoa(http) + "&tls=" + strconv.Itoa(tls) + "&socks=" + strconv.Itoa(socks) fmt.Printf("🔗 WebSocket连接URL: %s\n", fullURL) - reporter := NewWebSocketReporter(fullURL, Secret) // Pass Secret here + reporter := NewWebSocketReporter(fullURL, secret) reporter.Start() return reporter } diff --git a/gost.sql b/gost.sql index bb3a624..72dd94d 100644 --- a/gost.sql +++ b/gost.sql @@ -61,6 +61,9 @@ CREATE TABLE `node` ( `port_sta` int(10) NOT NULL, `port_end` int(10) NOT NULL, `version` varchar(100) DEFAULT NULL, + `http` int(10) NOT NULL DEFAULT '0', + `tls` int(10) NOT NULL DEFAULT '0', + `socks` int(10) NOT NULL DEFAULT '0', `created_time` bigint(20) NOT NULL, `updated_time` bigint(20) DEFAULT NULL, `status` int(10) NOT NULL diff --git a/panel_install.sh b/panel_install.sh index 1398151..3498685 100755 --- a/panel_install.sh +++ b/panel_install.sh @@ -537,6 +537,64 @@ UPDATE \`node\` SET \`port_sta\` = 1000, \`port_end\` = 65535 WHERE \`port_sta\` IS NULL OR \`port_end\` IS NULL; +-- node 表:添加 http、tls、socks 字段(如果不存在) +SET @sql = ( + SELECT IF( + NOT EXISTS ( + SELECT 1 + FROM information_schema.COLUMNS + WHERE table_schema = DATABASE() + AND table_name = 'node' + AND column_name = 'http' + ), + 'ALTER TABLE \`node\` ADD COLUMN \`http\` INT(10) DEFAULT 0 COMMENT "HTTP 服务端口";', + 'SELECT "Column \`http\` already exists in \`node\`";' + ) +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = ( + SELECT IF( + NOT EXISTS ( + SELECT 1 + FROM information_schema.COLUMNS + WHERE table_schema = DATABASE() + AND table_name = 'node' + AND column_name = 'tls' + ), + 'ALTER TABLE \`node\` ADD COLUMN \`tls\` INT(10) DEFAULT 0 COMMENT "TLS 服务端口";', + 'SELECT "Column \`tls\` already exists in \`node\`";' + ) +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = ( + SELECT IF( + NOT EXISTS ( + SELECT 1 + FROM information_schema.COLUMNS + WHERE table_schema = DATABASE() + AND table_name = 'node' + AND column_name = 'socks' + ), + 'ALTER TABLE \`node\` ADD COLUMN \`socks\` INT(10) DEFAULT 0 COMMENT "SOCKS 服务端口";', + 'SELECT "Column \`socks\` already exists in \`node\`";' + ) +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 为现有节点设置 http、tls、socks 默认值 +UPDATE \`node\` +SET \`http\` = IFNULL(\`http\`, 0), + \`tls\` = IFNULL(\`tls\`, 0), + \`socks\` = IFNULL(\`socks\`, 0); + -- tunnel 表:删除废弃字段(如果存在) SET @sql = ( SELECT IF( diff --git a/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java b/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java index 767b5a6..1d205a9 100644 --- a/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java +++ b/springboot-backend/src/main/java/com/admin/common/dto/NodeUpdateDto.java @@ -31,4 +31,8 @@ public class NodeUpdateDto { @Min(value = 1, message = "结束端口必须大于0") @Max(value = 65535, message = "结束端口不能超过65535") private Integer portEnd; + + private Integer http; + private Integer tls; + private Integer socks; } \ No newline at end of file diff --git a/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java b/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java index ed55321..40f9528 100644 --- a/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java +++ b/springboot-backend/src/main/java/com/admin/common/utils/WebSocketServer.java @@ -224,6 +224,9 @@ public class WebSocketServer extends TextWebSocketHandler { // 客户端节点连接 Long nodeId = Long.valueOf(id); String version = (String) session.getAttributes().get("nodeVersion"); + String http = (String) session.getAttributes().get("http"); + String tls = (String) session.getAttributes().get("tls"); + String socks = (String) session.getAttributes().get("socks"); log.info("节点 {} 尝试连接,开始处理连接逻辑", nodeId); @@ -256,6 +259,16 @@ public class WebSocketServer extends TextWebSocketHandler { if (version != null) { node.setVersion(version); } + if (http != null) { + node.setHttp(Integer.parseInt(http)); + } + if (tls != null) { + node.setTls(Integer.parseInt(tls)); + } + if (socks != null) { + node.setSocks(Integer.parseInt(socks)); + } + boolean updateResult = nodeService.updateById(node); if (updateResult) { diff --git a/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java b/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java index 08b3192..7a7964c 100644 --- a/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java +++ b/springboot-backend/src/main/java/com/admin/config/WebSocketInterceptor.java @@ -38,6 +38,9 @@ public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor { String secret = serverHttpRequest.getServletRequest().getParameter("secret"); String type = serverHttpRequest.getServletRequest().getParameter("type"); String version = serverHttpRequest.getServletRequest().getParameter("version"); + String http = serverHttpRequest.getServletRequest().getParameter("http"); + String tls = serverHttpRequest.getServletRequest().getParameter("tls"); + String socks = serverHttpRequest.getServletRequest().getParameter("socks"); if (Objects.equals(type, "1")) { System.out.println("type: " + type + " - version: " + version + " - secret: " + secret + " - IP: " + getClientIp(request)); Node node = nodeService.getOne(new QueryWrapper().eq("secret", secret)); @@ -48,6 +51,9 @@ public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor { attributes.put("id", node.getId()); attributes.put("nodeSecret", secret); attributes.put("nodeVersion", version); + attributes.put("http",http); + attributes.put("tls",tls); + attributes.put("socks",socks); log.info("节点 {} 通过验证,版本: {}", node.getId(), version); // 不在这里更新状态,等到连接建立后再统一更新 }else { diff --git a/springboot-backend/src/main/java/com/admin/entity/Node.java b/springboot-backend/src/main/java/com/admin/entity/Node.java index a4176d5..307b136 100644 --- a/springboot-backend/src/main/java/com/admin/entity/Node.java +++ b/springboot-backend/src/main/java/com/admin/entity/Node.java @@ -32,4 +32,10 @@ public class Node extends BaseEntity { private Integer portEnd; + private Integer http; + + private Integer tls; + + private Integer socks; + } diff --git a/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java index 6573732..7afbd90 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/NodeServiceImpl.java @@ -2,9 +2,11 @@ package com.admin.service.impl; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import com.admin.common.dto.GostDto; import com.admin.common.dto.NodeDto; import com.admin.common.dto.NodeUpdateDto; import com.admin.common.lang.R; +import com.admin.common.utils.WebSocketServer; import com.admin.entity.Node; import com.admin.entity.Tunnel; import com.admin.entity.ViteConfig; @@ -13,6 +15,7 @@ import com.admin.mapper.TunnelMapper; import com.admin.service.NodeService; import com.admin.service.TunnelService; import com.admin.service.ViteConfigService; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -22,6 +25,8 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; +import java.util.Objects; + import org.springframework.beans.factory.annotation.Value; /** @@ -114,10 +119,34 @@ public class NodeServiceImpl extends ServiceImpl implements No @Override public R updateNode(NodeUpdateDto nodeUpdateDto) { // 1. 验证节点是否存在 - if (!isNodeExists(nodeUpdateDto.getId())) { + Node node = this.getById(nodeUpdateDto.getId()); + if (node == null) { return R.err(ERROR_NODE_NOT_FOUND); } + //1.1 如果节点在线 且传入更新的 http/tls/socks 任意一项与数据库不一致,则通过 WS 通知节点更新设置 + boolean online = node.getStatus() != null && node.getStatus() == 1; + Integer newHttp = nodeUpdateDto.getHttp(); + Integer newTls = nodeUpdateDto.getTls(); + Integer newSocks = nodeUpdateDto.getSocks(); + + boolean httpChanged = newHttp != null && !newHttp.equals(node.getHttp()); + boolean tlsChanged = newTls != null && !newTls.equals(node.getTls()); + boolean socksChanged = newSocks != null && !newSocks.equals(node.getSocks()); + + if (online && (httpChanged || tlsChanged || socksChanged)) { + JSONObject req = new JSONObject(); + req.put("http", newHttp); + req.put("tls", newTls); + req.put("socks", newSocks); + + GostDto gostResult = WebSocketServer.send_msg(node.getId(), req, "SetProtocol"); + if (!Objects.equals(gostResult.getMsg(), "OK")){ + return R.err(gostResult.getMsg()); + } + } + + // 2. 构建更新对象并执行更新 Node updateNode = buildUpdateNode(nodeUpdateDto); boolean result = this.updateById(updateNode); @@ -153,7 +182,8 @@ public class NodeServiceImpl extends ServiceImpl implements No @Override public R deleteNode(Long id) { // 1. 验证节点是否存在 - if (!isNodeExists(id)) { + Node node = this.getById(id); + if (node == null) { return R.err(ERROR_NODE_NOT_FOUND); } @@ -225,7 +255,9 @@ public class NodeServiceImpl extends ServiceImpl implements No node.setServerIp(nodeUpdateDto.getServerIp()); node.setPortSta(nodeUpdateDto.getPortSta()); node.setPortEnd(nodeUpdateDto.getPortEnd()); - + node.setHttp(nodeUpdateDto.getHttp()); + node.setTls(nodeUpdateDto.getTls()); + node.setSocks(nodeUpdateDto.getSocks()); // 验证端口范围 validatePortRange(node.getPortSta(), node.getPortEnd()); @@ -242,15 +274,6 @@ public class NodeServiceImpl extends ServiceImpl implements No nodeList.forEach(node -> node.setSecret(null)); } - /** - * 检查节点是否存在 - * - * @param nodeId 节点ID - * @return 节点是否存在 - */ - private boolean isNodeExists(Long nodeId) { - return this.getById(nodeId) != null; - } /** * 检查节点使用情况 diff --git a/vite-frontend/.env.development b/vite-frontend/.env.development index a1c927f..2e92b25 100644 --- a/vite-frontend/.env.development +++ b/vite-frontend/.env.development @@ -1 +1 @@ -VITE_API_BASE=http://192.168.100.11:6365 +VITE_API_BASE=http://127.0.0.1:6365 diff --git a/vite-frontend/src/config/site.ts b/vite-frontend/src/config/site.ts index beb56fd..7f83a7c 100644 --- a/vite-frontend/src/config/site.ts +++ b/vite-frontend/src/config/site.ts @@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig; // 缓存相关常量 const CACHE_PREFIX = 'vite_config_'; -const VERSION = "1.4.0"; +const VERSION = "1.4.1"; const APP_VERSION = "1.0.3"; const getInitialConfig = () => { diff --git a/vite-frontend/src/pages/node.tsx b/vite-frontend/src/pages/node.tsx index dd2fff4..4688435 100644 --- a/vite-frontend/src/pages/node.tsx +++ b/vite-frontend/src/pages/node.tsx @@ -5,6 +5,7 @@ import { Input } from "@heroui/input"; import { Textarea } from "@heroui/input"; import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/modal"; import { Chip } from "@heroui/chip"; +import { Switch } from "@heroui/switch"; import { Spinner } from "@heroui/spinner"; import { Alert } from "@heroui/alert"; import { Progress } from "@heroui/progress"; @@ -28,6 +29,9 @@ interface Node { portSta: number; portEnd: number; version?: string; + http?: number; // 0 关 1 开 + tls?: number; // 0 关 1 开 + socks?: number; // 0 关 1 开 status: number; // 1: 在线, 0: 离线 connectionStatus: 'online' | 'offline'; systemInfo?: { @@ -49,6 +53,9 @@ interface NodeForm { serverIp: string; portSta: number; portEnd: number; + http: number; // 0 关 1 开 + tls: number; // 0 关 1 开 + socks: number; // 0 关 1 开 } export default function NodePage() { @@ -61,13 +68,18 @@ export default function NodePage() { const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); const [nodeToDelete, setNodeToDelete] = useState(null); + const [protocolDisabled, setProtocolDisabled] = useState(false); + const [protocolDisabledReason, setProtocolDisabledReason] = useState(''); const [form, setForm] = useState({ id: null, name: '', ipString: '', serverIp: '', portSta: 1000, - portEnd: 65535 + portEnd: 65535, + http: 0, + tls: 0, + socks: 0 }); const [errors, setErrors] = useState>({}); @@ -404,6 +416,8 @@ export default function NodePage() { setIsEdit(false); setDialogVisible(true); resetForm(); + setProtocolDisabled(true); + setProtocolDisabledReason('节点未在线,等待节点上线后再设置'); }; // 编辑节点 @@ -416,8 +430,14 @@ export default function NodePage() { ipString: node.ip ? node.ip.split(',').map(ip => ip.trim()).join('\n') : '', serverIp: node.serverIp || '', portSta: node.portSta, - portEnd: node.portEnd + portEnd: node.portEnd, + http: typeof node.http === 'number' ? node.http : 1, + tls: typeof node.tls === 'number' ? node.tls : 1, + socks: typeof node.socks === 'number' ? node.socks : 1 }); + const offline = node.connectionStatus !== 'online'; + setProtocolDisabled(offline); + setProtocolDisabledReason(offline ? '节点未在线,等待节点上线后再设置' : ''); setDialogVisible(true); }; @@ -514,7 +534,10 @@ export default function NodePage() { ip: ipString, serverIp: form.serverIp, portSta: form.portSta, - portEnd: form.portEnd + portEnd: form.portEnd, + http: form.http, + tls: form.tls, + socks: form.socks }; const res = await apiCall(data); @@ -530,7 +553,10 @@ export default function NodePage() { ip: ipString, serverIp: form.serverIp, portSta: form.portSta, - portEnd: form.portEnd + portEnd: form.portEnd, + http: form.http, + tls: form.tls, + socks: form.socks } : n )); } else { @@ -554,7 +580,10 @@ export default function NodePage() { ipString: '', serverIp: '', portSta: 1000, - portEnd: 65535 + portEnd: 65535, + http: 0, + tls: 0, + socks: 0 }); setErrors({}); }; @@ -869,8 +898,83 @@ export default function NodePage() { /> + {/* 屏蔽协议 */} +
+
屏蔽协议
+
开启开关以屏蔽对应协议
+ {protocolDisabled && ( + + )} +
+ {/* HTTP tile */} +
+
+ +
HTTP
+
+
+
禁用/启用
+ setForm(prev => ({ ...prev, http: v ? 1 : 0 }))} + /> +
+
{form.http === 1 ? '已开启' : '已关闭'}
+
+ + {/* TLS tile */} +
+
+ +
TLS
+
+
+
禁用/启用
+ setForm(prev => ({ ...prev, tls: v ? 1 : 0 }))} + /> +
+
{form.tls === 1 ? '已开启' : '已关闭'}
+
+ + {/* SOCKS tile */} +
+
+ +
SOCKS
+
+
+
禁用/启用
+ setForm(prev => ({ ...prev, socks: v ? 1 : 0 }))} + /> +
+
{form.socks === 1 ? '已开启' : '已关闭'}
+
+
+
+ +