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

  1. Make the upstream endpoint advertise its public IP in the SDP (configure its advertised/external address)
  2. 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
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/cfdbc949513d11f3. Report an issue: GitHub.