From fd6b5ddcddf987cb35e58d53f2867c53bab26986 Mon Sep 17 00:00:00 2001 From: qaq Date: Sat, 28 Jun 2025 23:24:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9A=82=E5=81=9Cbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go-gost/x/api/config_service.go | 8 -- go-gost/x/internal/util/port/port.go | 208 +++++++++------------------ go-gost/x/socket/service.go | 4 - 3 files changed, 66 insertions(+), 154 deletions(-) diff --git a/go-gost/x/api/config_service.go b/go-gost/x/api/config_service.go index bcf3143..12c8081 100644 --- a/go-gost/x/api/config_service.go +++ b/go-gost/x/api/config_service.go @@ -547,10 +547,6 @@ func pauseService(ctx *gin.Context) { } } - // 使用和 updateService 相同的方法彻底断开所有连接 - svc.Close() - registry.ServiceRegistry().Unregister(name) - // 强制断开端口的所有连接 if serviceAddr != "" { _ = kill.ForceClosePortConnections(serviceAddr) @@ -912,10 +908,6 @@ func pauseServices(ctx *gin.Context) { return } - // 使用和 updateService 相同的方法彻底断开所有连接 - stp.service.Close() - registry.ServiceRegistry().Unregister(stp.name) - // 强制断开端口的所有连接 if serviceConfig.Addr != "" { _ = kill.ForceClosePortConnections(serviceConfig.Addr) diff --git a/go-gost/x/internal/util/port/port.go b/go-gost/x/internal/util/port/port.go index f93bfb8..4c66d16 100644 --- a/go-gost/x/internal/util/port/port.go +++ b/go-gost/x/internal/util/port/port.go @@ -3,8 +3,9 @@ package port import ( "bufio" "fmt" - "net" "os" + "os/exec" + "path/filepath" "runtime" "strconv" "strings" @@ -16,178 +17,101 @@ func ForceClosePortConnections(addr string) error { return nil } - // 解析地址和端口 - host, portStr, err := net.SplitHostPort(addr) + _, portStr, err := splitHostPort(addr) if err != nil { - // 如果解析失败,可能是因为没有端口,或者格式不对 - return fmt.Errorf("failed to parse address %s: %v", addr, err) + return fmt.Errorf("parse address failed: %v", err) } port, err := strconv.Atoi(portStr) if err != nil { - return fmt.Errorf("invalid port %s: %v", portStr, err) + return fmt.Errorf("invalid port: %v", err) } - switch runtime.GOOS { - case "linux": - return forceClosePortConnectionsLinux(host, port) - default: - // 对于非Linux系统,静默忽略 + if runtime.GOOS != "linux" { + fmt.Println("Warning: current OS is not Linux, skipping connection close.") return nil } + + return closePortConnectionsBySS(port) } -// forceClosePortConnectionsLinux 在Linux上通过/proc/net/tcp*读取连接并关闭 -func forceClosePortConnectionsLinux(host string, port int) error { - // 关闭IPv4连接 - if err := closeConnectionsFromProcNet("/proc/net/tcp", port); err != nil { - fmt.Printf("Warning: failed to close IPv4 connections on port %d: %v\n", port, err) +// splitHostPort 是 net.SplitHostPort 的包装,容错增强 +func splitHostPort(addr string) (string, string, error) { + if !strings.Contains(addr, ":") { + return "", "", fmt.Errorf("missing port in address") } - - // 关闭IPv6连接 - if err := closeConnectionsFromProcNet("/proc/net/tcp6", port); err != nil { - fmt.Printf("Warning: failed to close IPv6 connections on port %d: %v\n", port, err) - } - - return nil + return netSplitHostPortCompat(addr) } -// closeConnectionsFromProcNet 从/proc/net/tcp*文件读取连接信息并关闭连接 -func closeConnectionsFromProcNet(procFile string, targetPort int) error { - file, err := os.Open(procFile) +// netSplitHostPortCompat 是增强版本的 SplitHostPort,兼容无 host 情况 +func netSplitHostPortCompat(addr string) (string, string, error) { + lastColon := strings.LastIndex(addr, ":") + if lastColon < 0 { + return "", "", fmt.Errorf("missing port") + } + return addr[:lastColon], addr[lastColon+1:], nil +} + +// closePortConnectionsBySS 通过 ss 找到 pid/fd 然后关闭连接 +func closePortConnectionsBySS(targetPort int) error { + cmd := exec.Command("ss", "-tnp") + out, err := cmd.Output() if err != nil { - return err + return fmt.Errorf("failed to execute ss: %v", err) } - defer file.Close() - - scanner := bufio.NewScanner(file) - // 跳过第一行(header) - scanner.Scan() + scanner := bufio.NewScanner(strings.NewReader(string(out))) for scanner.Scan() { line := scanner.Text() - fields := strings.Fields(line) - if len(fields) < 10 { + + if !strings.Contains(line, fmt.Sprintf(":%d", targetPort)) || !strings.Contains(line, "ESTAB") { continue } - // local_address字段格式: IP:PORT (hex) - localAddr := fields[1] - parts := strings.Split(localAddr, ":") - if len(parts) != 2 { + // 例如:users:(("gost",pid=1234,fd=17)) + start := strings.Index(line, "pid=") + if start == -1 { continue } - // 解析端口(十六进制) - portHex := parts[1] - port, err := strconv.ParseInt(portHex, 16, 32) + rest := line[start:] + parts := strings.Split(rest, ",") + if len(parts) < 2 { + continue + } + + pidStr := strings.TrimPrefix(parts[0], "pid=") + fdStr := strings.TrimPrefix(parts[1], "fd=") + pid := strings.Trim(pidStr, ")") + fd := strings.Trim(fdStr, ")") + + fdPath := fmt.Sprintf("/proc/%s/fd/%s", pid, fd) + realPath, err := filepath.EvalSymlinks(fdPath) if err != nil { + fmt.Printf("Warning: unable to resolve %s: %v\n", fdPath, err) continue } - // 检查是否是目标端口 - if int(port) == targetPort { - // 获取远程地址信息 - remoteAddr := fields[2] - state := fields[3] - - // 只关闭已建立的连接,跳过监听状态 - if state == "0A" { // LISTEN状态 - continue - } - - // 尝试通过socket操作关闭连接 - if err := closeConnectionByAddresses(localAddr, remoteAddr); err != nil { - fmt.Printf("Warning: failed to close connection %s->%s: %v\n", localAddr, remoteAddr, err) - } + // 再次确认是 socket + if !strings.Contains(realPath, "socket:") { + continue } + + // 尝试关闭 fd + func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Warning: panic closing %s: %v\n", fdPath, r) + } + }() + f, err := os.OpenFile(fdPath, os.O_RDWR, 0) + if err != nil { + fmt.Printf("Warning: open fd failed: %v\n", err) + return + } + defer f.Close() + }() } return scanner.Err() } - -// closeConnectionByAddresses 通过地址信息关闭连接 -func closeConnectionByAddresses(localAddr, remoteAddr string) error { - // 解析本地地址 - localParts := strings.Split(localAddr, ":") - if len(localParts) != 2 { - return fmt.Errorf("invalid local address format") - } - - // 解析远程地址 - remoteParts := strings.Split(remoteAddr, ":") - if len(remoteParts) != 2 { - return fmt.Errorf("invalid remote address format") - } - - // 转换十六进制IP和端口 - localIP, err := hexToIP(localParts[0]) - if err != nil { - return err - } - - localPort, err := strconv.ParseInt(localParts[1], 16, 32) - if err != nil { - return err - } - - remoteIP, err := hexToIP(remoteParts[0]) - if err != nil { - return err - } - - remotePort, err := strconv.ParseInt(remoteParts[1], 16, 32) - if err != nil { - return err - } - - // 创建连接并立即关闭来触发RST - localAddr4 := fmt.Sprintf("%s:%d", localIP, localPort) - remoteAddr4 := fmt.Sprintf("%s:%d", remoteIP, remotePort) - - return sendRSTToConnection(localAddr4, remoteAddr4) -} - -// hexToIP 将十六进制IP地址转换为可读格式 -func hexToIP(hexIP string) (string, error) { - if len(hexIP) == 8 { // IPv4 - ip := make([]byte, 4) - for i := 0; i < 4; i++ { - val, err := strconv.ParseUint(hexIP[i*2:(i+1)*2], 16, 8) - if err != nil { - return "", err - } - ip[3-i] = byte(val) // 小端序 - } - return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]), nil - } - // 对于IPv6,处理更复杂,这里简化处理 - return "", fmt.Errorf("IPv6 not implemented yet") -} - -// sendRSTToConnection 向指定连接发送RST包 -func sendRSTToConnection(localAddr, remoteAddr string) error { - // 尝试创建一个到远程地址的连接,然后立即使用SO_LINGER=0关闭 - // 这会发送RST包而不是正常的FIN包 - conn, err := net.Dial("tcp", remoteAddr) - if err != nil { - // 如果无法连接,说明连接可能已经不存在了 - return nil - } - - // 获取底层的TCP连接 - tcpConn, ok := conn.(*net.TCPConn) - if !ok { - conn.Close() - return fmt.Errorf("not a TCP connection") - } - - // 设置SO_LINGER为0,这样关闭时会发送RST而不是FIN - if err := tcpConn.SetLinger(0); err != nil { - tcpConn.Close() - return err - } - - // 关闭连接,这会发送RST包 - return tcpConn.Close() -} diff --git a/go-gost/x/socket/service.go b/go-gost/x/socket/service.go index e893185..e8a8750 100644 --- a/go-gost/x/socket/service.go +++ b/go-gost/x/socket/service.go @@ -274,10 +274,6 @@ func pauseServices(req pauseServicesRequest) error { return errors.New(fmt.Sprintf("service %s configuration not found", stp.name)) } - // 使用和 updateService 相同的方法彻底断开所有连接 - stp.service.Close() - registry.ServiceRegistry().Unregister(stp.name) - // 强制断开端口的所有连接 if serviceConfig.Addr != "" { _ = kill.ForceClosePortConnections(serviceConfig.Addr)