fix(portal): add TCP port exhaustion error and split SupportsRawTCP field
- Add errTCPPortExhausted sentinel error so TCP port exhaustion returns APIErrorCodeTCPPortExhausted instead of the UDP error code. - Add SupportsRawTCP field for raw TCP port routing capability. - Restore SupportsTCP to always true (deprecated, TLS-based TCP stream). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Yechan Kim committed
Apr 3, 2026 at 03:06 UTC
3e38a7de0756b529479f773fdafd78f35d99ce82
2 files changed
+10
-2
portal/api_server.go
+8
-1
@@ -37,6 +37,7 @@ var (
37
errUDPCapacityExceeded = errors.New(types.APIErrorCodeUDPCapacityExceeded)
38
errTCPPortDisabled = errors.New(types.APIErrorCodeTCPPortDisabled)
39
errTCPPortCapacityExceeded = errors.New(types.APIErrorCodeTCPPortCapacityExceeded)
40
+ errTCPPortExhausted = errors.New("no tcp ports available")
41
)
42
43
func (s *Server) newAPIServer(listener net.Listener, apiMux *http.ServeMux, apiTLS keyless.TLSMaterialConfig) (net.Listener, *http.Server, io.Closer, error) {
@@ -159,7 +160,8 @@ func (s *Server) handleRelayDiscovery(w http.ResponseWriter, r *http.Request) {
160
IngressTLSAddr: ingressAddr,
161
SupportsTLS: true,
162
SupportsUDP: s.cfg.UDPPortCount > 0,
162
- SupportsTCP: s.cfg.TCPPortCount > 0,
163
+ SupportsTCP: true,
164
+ SupportsRawTCP: s.cfg.TCPPortCount > 0,
165
SupportsOverlayPeer: supportsOverlayPeer,
166
WireGuardPublicKey: s.wgConfig.PublicKey,
167
WireGuardEndpoint: s.wgConfig.Endpoint,
@@ -231,6 +233,8 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
233
utils.WriteAPIError(w, http.StatusConflict, types.APIErrorCodeHostnameConflict, err.Error())
234
case errors.Is(err, errIPBanned):
235
utils.WriteAPIError(w, http.StatusForbidden, types.APIErrorCodeIPBanned, err.Error())
236
+ case errors.Is(err, errTCPPortExhausted):
237
+ utils.WriteAPIError(w, http.StatusServiceUnavailable, types.APIErrorCodeTCPPortExhausted, err.Error())
238
case errors.Is(err, transport.ErrPortExhausted):
239
utils.WriteAPIError(w, http.StatusServiceUnavailable, types.APIErrorCodeUDPPortExhausted, err.Error())
240
case errors.Is(err, errUDPDisabled):
@@ -616,6 +620,9 @@ func (s *Server) registerLease(req types.RegisterChallengeRequest, clientIP, rep
620
}
621
port, err := s.tcpPorts.Allocate(identity.Name)
622
if err != nil {
623
+ if errors.Is(err, transport.ErrPortExhausted) {
624
+ return types.RegisterResponse{}, errTCPPortExhausted
625
+ }
626
return types.RegisterResponse{}, err
627
}
628
record.tcpPort = transport.NewRelayTCPPort(identityKey, port, stream)
types/identity.go
+2
-1
@@ -94,7 +94,8 @@ type RelayDescriptor struct {
94
95
SupportsTLS bool `json:"supports_tls,omitempty"`
96
SupportsUDP bool `json:"supports_udp,omitempty"`
97
- SupportsTCP bool `json:"supports_tcp,omitempty"`
97
+ SupportsTCP bool `json:"supports_tcp,omitempty"` // Deprecated: always true. Use SupportsRawTCP instead.
98
+ SupportsRawTCP bool `json:"supports_raw_tcp,omitempty"`
99
SupportsOverlayPeer bool `json:"supports_overlay_peer,omitempty"`
100
}
101