package api import ( "fmt" "net" "strconv" "github.com/gin-gonic/gin" "vpn-platform/backend/internal/model" "vpn-platform/backend/internal/service" "vpn-platform/backend/internal/store" ) // nextOIDCPort suggests a free loopback listener; starting the tunnel still // checks the port again because another process may bind it in the meantime. func (s *Server) nextOIDCPort(c *gin.Context) { var tunnels []model.Tunnel if err := store.DB.Where("protocol = ?", "openvpn").Find(&tunnels).Error; err != nil { c.JSON(500, gin.H{"error": "无法读取已配置的 OpenVPN 专线"}) return } reserved := make(map[int]bool) maxConfigured := 19089 for _, tunnel := range tunnels { cfg, err := store.TunnelConfig(&tunnel) if err != nil { continue } if service.Str(cfg, "proto") == "tcp" || service.Str(cfg, "proto") == "tcp-server" { reserved[int(service.Num(cfg, "port", 1194))] = true } if service.Str(cfg, "auth_mode") != "oidc" { continue } port := int(service.Num(cfg, "oidc_http_port", 19090)) reserved[port] = true if port >= 19090 && port > maxConfigured { maxConfigured = port } } start, direction := maxConfigured+1, 1 if start > 65535 { start = 19090 } if from := c.Query("from"); from != "" { var err error start, err = strconv.Atoi(from) if err != nil || start < 1024 || start > 65535 { c.JSON(400, gin.H{"error": "端口必须为 1024-65535"}) return } if c.Query("direction") == "-1" { direction = -1 } else if c.Query("direction") != "" && c.Query("direction") != "1" { c.JSON(400, gin.H{"error": "方向必须为 1 或 -1"}) return } } if port, ok := findAvailableOIDCPort(start, direction, reserved, loopbackTCPAvailable); ok { c.JSON(200, gin.H{"port": port}) return } c.JSON(409, gin.H{"error": "该方向没有可用的本机认证端口,请手动选择其他起始端口"}) } func findAvailableOIDCPort(start, direction int, reserved map[int]bool, available func(int) bool) (int, bool) { for port := start; port >= 1024 && port <= 65535; port += direction { if !reserved[port] && available(port) { return port, true } } return 0, false } func loopbackTCPAvailable(port int) bool { listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { return false } _ = listener.Close() return true }