feat(tcp): Fix reconnect behavior to keep stable subdomain and TCP port

Persist the assigned subdomain after first connect so reconnects reuse it.
Allow reserving a specific TCP port when the subdomain is tcp-<port> to prevent port drift.
This commit is contained in:
Gouryella
2025-12-20 10:25:13 +08:00
parent ddefbbdbd9
commit b75a098f99
3 changed files with 64 additions and 8 deletions

View File

@@ -58,6 +58,12 @@ func runTunnelWithUI(connConfig *tcp.ConnectorConfig, daemonInfo *DaemonInfo) er
}
reconnectAttempts = 0
if assignedSubdomain := connector.GetSubdomain(); assignedSubdomain != "" {
connConfig.Subdomain = assignedSubdomain
if daemonInfo != nil {
daemonInfo.Subdomain = assignedSubdomain
}
}
if daemonInfo != nil {
daemonInfo.URL = connector.GetURL()

View File

@@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"
@@ -131,15 +132,24 @@ func (c *Connection) Handle() error {
return fmt.Errorf("port allocator not configured")
}
port, err := c.portAlloc.Allocate()
if err != nil {
c.sendError("port_allocation_failed", err.Error())
return fmt.Errorf("failed to allocate port: %w", err)
}
c.port = port
if requestedPort, ok := parseTCPSubdomainPort(req.CustomSubdomain); ok {
port, err := c.portAlloc.AllocateSpecific(requestedPort)
if err != nil {
c.sendError("port_allocation_failed", err.Error())
return fmt.Errorf("failed to allocate requested port %d: %w", requestedPort, err)
}
c.port = port
} else {
port, err := c.portAlloc.Allocate()
if err != nil {
c.sendError("port_allocation_failed", err.Error())
return fmt.Errorf("failed to allocate port: %w", err)
}
c.port = port
if req.CustomSubdomain == "" {
req.CustomSubdomain = fmt.Sprintf("tcp-%d", port)
if req.CustomSubdomain == "" {
req.CustomSubdomain = fmt.Sprintf("tcp-%d", port)
}
}
}
@@ -383,6 +393,24 @@ func min(a, b int) int {
return b
}
func parseTCPSubdomainPort(subdomain string) (int, bool) {
if !strings.HasPrefix(subdomain, "tcp-") {
return 0, false
}
portStr := strings.TrimPrefix(subdomain, "tcp-")
if portStr == "" {
return 0, false
}
port, err := strconv.Atoi(portStr)
if err != nil || port < 1 || port > 65535 {
return 0, false
}
return port, true
}
func (c *Connection) handleFrames(reader *bufio.Reader) error {
for {
select {

View File

@@ -56,6 +56,28 @@ func (p *PortAllocator) Allocate() (int, error) {
return 0, fmt.Errorf("no available port in range %d-%d", p.min, p.max)
}
// AllocateSpecific reserves a specific port if it is within range and available.
func (p *PortAllocator) AllocateSpecific(port int) (int, error) {
p.mu.Lock()
defer p.mu.Unlock()
if port < p.min || port > p.max {
return 0, fmt.Errorf("requested port %d outside range %d-%d", port, p.min, p.max)
}
if p.used[port] {
return 0, fmt.Errorf("requested port %d already in use", port)
}
ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
return 0, fmt.Errorf("requested port %d unavailable: %w", port, err)
}
_ = ln.Close()
p.used[port] = true
return port, nil
}
// Release frees a previously allocated port.
func (p *PortAllocator) Release(port int) {
p.mu.Lock()