feat(openvpn): suggest available OIDC ports and add deployment assets
This commit is contained in:
1 parent
d6a9a92ad0
commit
003f755b37
14 files changed
+244
-7
No files matched your search
@@ -230,6 +230,7 @@ func (s *Server) RegisterRoutes(r *gin.Engine) {
|
||||
auth.GET("/readiness", s.readiness)
|
||||
auth.GET("/protocols", s.listProtocols)
|
||||
auth.GET("/tunnels", s.listTunnels)
|
||||
auth.GET("/tunnels/openvpn/oidc-port", s.nextOIDCPort)
|
||||
auth.GET("/tunnels/:id", s.getTunnel)
|
||||
auth.GET("/tunnels/:id/status", s.tunnelStatus)
|
||||
auth.GET("/tunnels/:id/traffic/history", s.tunnelTrafficHistory)
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFindAvailableOIDCPortSkipsReservedAndOccupied(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Skipf("当前环境不允许监听回环端口: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
occupied := listener.Addr().(*net.TCPAddr).Port
|
||||
if occupied < 1024 || occupied > 65533 {
|
||||
t.Skip("系统分配的临时端口无法用于递增测试")
|
||||
}
|
||||
reserved := map[int]bool{occupied + 1: true}
|
||||
if loopbackTCPAvailable(occupied) {
|
||||
t.Fatal("监听中的端口必须视为占用")
|
||||
}
|
||||
available := func(port int) bool { return port != occupied }
|
||||
port, ok := findAvailableOIDCPort(occupied, 1, reserved, available)
|
||||
if !ok || port != occupied+2 {
|
||||
t.Fatalf("递增应跳过已监听及已配置的端口,得到 %d (ok=%t)", port, ok)
|
||||
}
|
||||
port, ok = findAvailableOIDCPort(occupied+1, -1, reserved, available)
|
||||
if !ok || port != occupied-1 {
|
||||
t.Fatalf("递减应跳过已配置及已监听的端口,得到 %d (ok=%t)", port, ok)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user