router-for-me/CLIProxyAPI · error

upstream WebRTC answer has no supported public TCP passive c

Error message

upstream WebRTC answer has no supported public TCP passive candidate on port 443

What it means

Thrown by prepareProxiedUpstreamAnswer when a TCP proxy dialer is configured and the upstream Codex WebRTC answer was parsed and filtered, but zero candidate lines survived. The proxy path only rewrites 'host/tcp/passive/RTP-component' candidates listening on port 443; if the upstream returned only UDP candidates, srflx/relay candidates, active TCP, or TCP on another port, plans ends up empty and the error is returned.

Source

Thrown at internal/client/codex/live/tcp_proxy.go:147

			plan, keep, errCandidate := proxiedTCPCandidatePlan(attribute.Value)
			if errCandidate != nil {
				return "", nil, errCandidate
			}
			if !keep {
				continue
			}
			if len(plans) >= maxProxiedTCPCandidates {
				return "", nil, fmt.Errorf("upstream WebRTC answer exceeds the %d TCP candidate proxy limit", maxProxiedTCPCandidates)
			}
			plan.mediaIndex = mediaIndex
			plan.attributeIndex = len(filtered)
			filtered = append(filtered, attribute)
			plans = append(plans, plan)
		}
		media.Attributes = filtered
	}
	if len(plans) == 0 {
		return "", nil, errors.New("upstream WebRTC answer has no supported public TCP passive candidate on port 443")
	}

	expectedUser := remoteCredentials.ufrag + ":" + localCredentials.ufrag
	tunnels := make([]*tcpCandidateTunnel, 0, len(plans))
	closeTunnels := func() {
		for _, tunnel := range tunnels {
			if errClose := tunnel.Close(); errClose != nil {
				log.WithError(errClose).Debug("codex live TCP proxy: close candidate tunnel after setup error")
			}
		}
	}
	for _, plan := range plans {
		tunnel, errTunnel := newTCPCandidateTunnel(plan.target, dialer, expectedUser, remoteCredentials.password)
		if errTunnel != nil {
			closeTunnels()
			return "", nil, errTunnel
		}
		tunnels = append(tunnels, tunnel)

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Capture the raw upstream answer SDP (log it at debug level) and inspect the a=candidate lines for tcp passive port 443 entries
  2. If the upstream no longer offers TCP passive on 443, disable the TCP proxy dialer for this session so the relay uses direct connectivity
  3. Verify the proxy dialer configuration actually points at a proxy the upstream supports — the server may serve different candidate sets depending on how the signaling connection was established
  4. Report upstream candidate-set changes to the maintainers if the SDP genuinely lacks supported candidates
Defensive patterns

Strategy: fallback

Try / catch

sdp, tunnels, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer)
if err != nil && strings.Contains(err.Error(), "no supported public TCP passive candidate on port 443") {
    log.Warn("upstream lacks proxiable TCP candidates; falling back to direct connection")
    sdp, tunnels, err = answer, nil, nil // apply the raw answer without proxying
}
if err != nil {
    return err
}

Prevention

When it happens

Trigger: Configuring the live media relay to dial the upstream through an HTTP CONNECT/SOCKS TCP proxy (proxyDialer != nil) while the upstream server's SDP contains no a=candidate ... tcp ... passive ... 443 host line.

Common situations: Upstream Codex endpoint changes its ICE strategy (e.g. prefers UDP-only or moves off port 443); a proxy environment where the server behind it advertises non-standard candidates; region-specific infrastructure differences.

Related errors


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