router-for-me/CLIProxyAPI · error
upstream WebRTC TCP proxy candidate address must be globally
Error message
upstream WebRTC TCP proxy candidate address must be globally routable
What it means
Thrown when a proxied TCP candidate parsed to a valid IP but isPublicProxyTarget() rejected it: the address is not globally routable — it is private (RFC1918), loopback, link-local, multicast, unspecified, or not global-unicast. The proxy deliberately refuses to forward to non-public addresses to avoid becoming an open relay into private networks.
Source
Thrown at internal/client/codex/live/tcp_proxy.go:209
if candidate.NetworkType() != ice.NetworkTypeTCP4 && candidate.NetworkType() != ice.NetworkTypeTCP6 {
return tcpCandidatePlan{}, false, nil
}
if candidate.TCPType() != ice.TCPTypePassive {
return tcpCandidatePlan{}, false, nil
}
if candidate.Component() != uint16(ice.ComponentRTP) || candidate.Type() != ice.CandidateTypeHost {
return tcpCandidatePlan{}, false, nil
}
if candidate.Port() != 443 {
return tcpCandidatePlan{}, false, fmt.Errorf("upstream WebRTC TCP proxy candidate uses disallowed port %d", candidate.Port())
}
address, errAddress := netip.ParseAddr(candidate.Address())
if errAddress != nil {
return tcpCandidatePlan{}, false, errors.New("upstream WebRTC TCP proxy candidate address must be an IP")
}
address = address.Unmap()
if !isPublicProxyTarget(address) {
return tcpCandidatePlan{}, false, errors.New("upstream WebRTC TCP proxy candidate address must be globally routable")
}
fields := strings.Fields(trimmed)
if len(fields) < 8 {
return tcpCandidatePlan{}, false, errors.New("upstream WebRTC TCP proxy candidate is malformed")
}
return tcpCandidatePlan{
fields: fields,
target: netip.AddrPortFrom(address, uint16(candidate.Port())),
}, true, nil
}
func isPublicProxyTarget(address netip.Addr) bool {
if !address.IsValid() || !address.IsGlobalUnicast() || address.IsUnspecified() || address.IsLoopback() ||
address.IsPrivate() || address.IsLinkLocalUnicast() || address.IsLinkLocalMulticast() || address.IsMulticast() {
return false
}
for _, prefix := range nonRoutableProxyTargetPrefixes {
if prefix.Contains(address) {View on GitHub (pinned to 78f0c4079e)
Solutions
- Make the upstream endpoint advertise its public IP in the SDP (configure its advertised/external address)
- If you intentionally need private-address proxying in a controlled environment, review the codex.live-media-relay private-IP settings and network posture — note the proxy still rejects non-routable targets by design
- Otherwise disable TCP proxying for this session and connect directly on the private network
Defensive patterns
Strategy: validation
Validate before calling
// Verify the candidate target is globally routable before proxying
fields := strings.Fields(candidateLine)
addr, err := netip.ParseAddr(fields[4])
if err != nil || !addr.Unmap().IsGlobalUnicast() || addr.IsPrivate() || addr.IsLoopback() || addr.IsLinkLocalUnicast() {
log.Warn().Str("candidate", candidateLine).Msg("non-routable candidate; proxy will reject")
} Type guard
func isProxyableCandidateAddr(s string) bool {
addr, err := netip.ParseAddr(s)
if err != nil {
return false
}
addr = addr.Unmap()
return addr.IsValid() && addr.IsGlobalUnicast() && !addr.IsUnspecified() &&
!addr.IsLoopback() && !addr.IsPrivate() && !addr.IsLinkLocalUnicast() &&
!addr.IsLinkLocalMulticast() && !addr.IsMulticast()
} Try / catch
if _, _, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {
if strings.Contains(err.Error(), "globally routable") {
return fmt.Errorf("upstream advertises private address in SDP; configure its public IP: %w", err)
}
return err
} Prevention
- Configure upstream WebRTC endpoints to advertise their public/external IP
- Never point the TCP proxy at NAT-internal addresses; it refuses by design as an anti-SSRF measure
- In lab setups, run the media path on routable addresses or disable proxying
When it happens
Trigger: Upstream SDP advertises a host candidate with an internal address (10.x, 192.168.x, 127.0.0.1, 169.254.x, ::1, fc00::/7, etc.) on TCP passive port 443 while the TCP proxy dialer is enabled.
Common situations: The 'server' is actually behind NAT and leaks its LAN address into the SDP; a test rig advertises loopback candidates; the explicitly named disable-private-remote-ips / allow-private-remote-ips relay settings interact with deployments that genuinely need private targets.
Related errors
- upstream WebRTC answer has no supported public TCP passive c
- upstream WebRTC TCP proxy candidate address must be an IP
- upstream WebRTC TCP proxy candidate is malformed
- Codex live TCP proxy listener returned an invalid address
- SDP contains incomplete ICE credentials
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/cfdbc949513d11f3.
Report an issue: GitHub.