Skip to content

esp_peer: ICE connectivity checks never use TURN relay candidate when connecting to remote LiveKit SFU (ESP32-S3) #104

Description

@star409964

LiveKit SDK Version

v0.3.9

IDF Version

v6.0.1

Issue Description


Body

Summary

When connecting an ESP32-S3 device to a remote LiveKit server over the public internet, WebRTC media (ICE) fails with an RTC timeout. Signaling (WebSocket + protobuf) works correctly, and TURN allocation succeeds, but the ICE agent only performs connectivity checks using the local host candidate (cand:0, private LAN IP). It never uses the allocated relay candidate (cand:2) or srflx candidate (cand:1).

The same firmware connects successfully to a local LiveKit server on the same LAN (host-to-host ICE works).

This appears to be an esp_peer ICE candidate pairing / connectivity-check issue in the prebuilt libpeer_default.a, not a server-side TURN port configuration issue.


Environment

Item Version
Chip ESP32-S3
ESP-IDF v6.0.1
esp_peer 1.4.1 (from ESP Component Registry)
LiveKit ESP32 SDK 0.3.9
Integration LiveKit engine.cesp_peer_open() with ESP_PEER_ICE_TRANS_POLICY_ALL
Remote server LiveKit SFU + embedded TURN (turn:36.131.215.25:3478?transport=udp)
Local network ESP32 behind NAT (e.g. 192.168.3.175), public IP via STUN ~182.200.13.118

Steps to Reproduce

  1. Flash ESP32-S3 firmware using LiveKit SDK 0.3.9 + esp_peer 1.4.1.
  2. Connect to a remote LiveKit room over the internet (wss://...).
  3. Join succeeds; Answer + ICE trickle received.
  4. Observe ICE agent logs: repeated Send binding request (cand:0) from private host IP to remote SFU ports on public IP.
  5. After ~10–15 s: peer state CONNECTING → FAILED, failure reason: RTC.

Control test: Same firmware connecting to a local LiveKit server (192.168.3.x) succeeds; ICE checks use host candidates and complete quickly.


Expected Behavior

When connecting to a remote SFU over the internet:

  • ICE should eventually use relay-to-relay or srflx-to-host candidate pairs, e.g.:
    • local relay (36.x.x.x:52827) ↔ remote SFU (36.x.x.x:56935)
  • Or at minimum, after host checks fail, fall back to cand:1 / cand:2.

With ESP_PEER_ICE_TRANS_POLICY_RELAY (force_relay), local relay should be able to reach remote host/srflx candidates via TURN (standard WebRTC iceTransportPolicy: relay semantics).


Actual Behavior

Case A: ESP_PEER_ICE_TRANS_POLICY_ALL (default)

  • TURN Allocate succeeds; SDP includes relay candidate:
    a=candidate:2 ... 36.131.215.25 52827 typ relay
    
  • Remote trickle adds SFU candidates (Add remote type:1 36.131.215.25:56935).
  • ICE checks only use cand:0 (host):
    AGENT: 0 0 Send binding request (cand:0) local0:192.168.3.175:64367 remote0:36.131.215.25:56935
    AGENT: 0 1 Send binding request (cand:0) local0:192.168.3.175:64367 remote0:36.131.215.25:58945
    
  • No logs for cand:1 or cand:2 binding requests.
  • ~15 s later: State changed: 1 -> 4, RTC timeout.

Case B: ESP_PEER_ICE_TRANS_POLICY_RELAY (force_relay = true)

  • SDP contains only relay local candidate (typ relay).
  • Remote still sends type:1 (host/srflx on public IP) candidates.
  • ICE fails immediately (~9 ms), no Send binding request logs at all.
  • Suggests no valid candidate pairs are formed when local is relay-only and remote is host/srflx.

Additional notes

  • TCP TURN is skipped by esp_peer:
    W AGENT: Skip TCP stun server turns:livekit-turn.shengshitong.com:443?transport=tcp
    
  • Browser clients on the same network connect via TCP to SFU port 7881 (182.x.x.x:xxxx ↔ 36.x.x.x:7881), which ESP32 cannot use.

Relevant Logs (abbreviated)

I livekit_signaling: Join: room=6666 ... ice_servers=1
I livekit_signaling: Join.ice[0][0]: turn:36.131.215.25:3478?transport=udp
I livekit_engine: ICE force_relay: server=0 kconfig=0 effective=0

I AGENT: 0 Get candidate success ...
W AGENT: Skip TCP stun server turns:livekit-turn.shengshitong.com:443?transport=tcp

Generated offer includes:
  a=candidate:0 ... 192.168.3.175 64367 typ host
  a=candidate:1 ... 182.200.13.118 5083 typ srflx
  a=candidate:2 ... 36.131.215.25 52827 typ relay

I AGENT: 0 Add remote type:1 36.131.215.25:56935
I AGENT: 0 Add remote type:1 36.131.215.25:58945
I livekit_peer.pub: State changed: 0 -> 1

I AGENT: 0 0 Send binding request (cand:0) local0:192.168.3.175:64367 remote0:36.131.215.25:56935
... (repeated for ~15s, only cand:0, no response)

I livekit_peer.pub: State changed: 1 -> 4
E voice_session: failure reason: RTC

Server / TURN Verification (already ruled out)

We verified on the server side:

  • TURN UDP 3478 reachable; Allocate succeeds on device.
  • TURN relay ports configured in range 50000–55000; allocated relay (e.g. 52827) is in range.
  • Firewall allows the relay port range.

Packet capture showed the issue is on the client ICE path selection, not missing server relay ports.


Questions

  1. Is it expected that with ESP_PEER_ICE_TRANS_POLICY_ALL, esp_peer only checks cand:0 (host) against remote relay/host candidates, and never tries local relay (cand:2) within the ICE timeout window?
  2. With ESP_PEER_ICE_TRANS_POLICY_RELAY, should local relay be pairable with remote host candidates (standard WebRTC relay policy)? If yes, is the instant failure (~9 ms, no binding requests) a known bug?
  3. Is there a recommended esp_peer_default_cfg_t or other configuration to force relay candidate use for connectivity checks against a remote SFU?
  4. Is TCP TURN (turns:443?transport=tcp) or SFU TCP media (e.g. port 7881) planned for ESP32?

Workaround

  • Local LiveKit server on same LAN: works (host ICE).
  • Remote cloud LiveKit: not working with current esp_peer 1.4.1.

Thank you for any guidance or fixes. Happy to provide full serial logs or pcaps if needed.


Additional context (optional, fill before submit)

  • Project: esp32s3-voice-ai (ESP32-S3 voice AI + LiveKit)
  • LiveKit server: self-hosted cloud instance
  • Full serial log: attach *.txt if needed

Hardware Configuration

  • Project: esp32s3-voice-ai (ESP32-S3 voice AI + LiveKit)
  • LiveKit server: self-hosted cloud instance
  • Full serial log: attach *.txt if needed

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions