rapid-chef-41237
03/26/2026, 3:06 AMlivekit/livekit=0.3.6 on an ESP32-S3 Lichuang board.
Current setup:
- Board: lichuang_esp32s3
- ESP-IDF: v5.5.3
- SDK dependency: livekit/livekit=0.3.6
- LiveKit Cloud URL: <wss://bo-996enr6g.livekit.cloud>
- Auth during development: LiveKit sandbox token server
- Agent dispatch: Jordan-19de
- ICE setting: relay-only enabled
I already confirmed these parts are working:
- Wi-Fi connects successfully
- The device gets an IP address, for example 10.0.0.44
- LiveKit signaling connects successfully
- TLS certificate validation succeeds
- 401 Unauthorized is no longer happening after switching away from an embedded expiring JWT
- A desktop client on the same network can join the same LiveKit project and talk to the same agent normally
The remaining problem is that the ESP32 reaches the RTC phase, then disconnects and starts reconnecting.
Representative serial log sequence:
text
livekit_signaling: Connecting to server: <wss://bo-996enr6g.livekit.cloud/rtc>?...
esp-x509-crt-bundle: Certificate validated
DTLS: Init SRTP OK
livekit_engine: Subscribing to audio track
AGENT: Start agent as Controlling/Controlled
Get candidate success ...
TURN relay candidates acquired from 140.238.63.214:...
Skip TCP stun server turns:otokyo1b.turn.livekit.cloud:443?transport=tcp
Skip TCP stun server turns:bo-996enr6g.turn.livekit.cloud:443?transport=tcp
publisher offer generated
subscriber answer generated
peer state: 1 -> 4
transport_ws: esp_transport_ws_poll_connection_closed: unexpected data readable on socket=54
websocket_client: Connection terminated while waiting for clean TCP close
UDP: Failed to select: Bad file number
livekit_engine: Reconnect ... reason=4
livekit_room_v2: Failure reason: RTC
What seems important to me:
- This is no longer an auth problem.
- The device gets past signaling and into DTLS/SRTP and ICE/TURN work.
- TURN relay candidates are acquired successfully.
- The failure seems to happen after SDP exchange begins and peer state advances.
- Then the WebSocket side closes unexpectedly, and sometimes the UDP path reports Bad file number.
My questions:
1. Is this a known issue or limitation for livekit/livekit=0.3.6 on ESP32-S3?
2. Are there recommended esp_peer / ICE settings for LiveKit Cloud on ESP32-S3, especially for relay-only mode?
3. Is skipping TCP TURN candidates expected here, or could that be related?
4. Does transport_ws: unexpected data readable on socket usually indicate a signaling transport problem, or is it often secondary to an RTC teardown?
5. Has anyone seen UDP: Failed to select: Bad file number in the ESP32 client during reconnect or peer restart?
Any pointers on where to inspect next in esp_peer, ICE transport, or the LiveKit ESP32 client would help.tall-airline-16756
03/26/2026, 3:07 AMtall-airline-16756
03/26/2026, 3:07 AMtransport_ws: unexpected data readable on socket usually indicate a signaling transport problem, or is it often secondary to an RTC teardown?
• This log often appears as a symptom, not necessarily the root cause. Common patterns:
◦ A media/ICE/DTLS failure triggers the client to teardown and reconnect signaling; the websocket stack then notices unexpected data or an unclean state and logs that message.
◦ Alternatively, a real signaling-layer interruption can also cause the RTC to tear down.
• Given your sequence (DTLS/SRTP OK, TURN candidates acquired, SDP exchange, then websocket close), it’s more likely the websocket closure is secondary — a media transport failure (ICE/DTLS/UDP socket lifecycle) causes the higher-level connection to restart, and the websocket stack logs the socket state error during the teardown/reconnect.
• To verify: enable verbose websocket/signaling logging and correlate timestamps with DTLS/ICE logs. If websocket close entries come after ICE/DTLS errors, the websocket message is secondary.
5) Has anyone seen UDP: Failed to select: Bad file number in the ESP32 client during reconnect or peer restart?
• That exact message isn’t documented as a LiveKit-specific error, but it strongly suggests a socket/file-descriptor lifecycle problem at the LWIP/esp-idf level (for example, using a socket after it was closed or not initialized).
• Likely causes:
◦ A socket was closed while select()/poll still tried to use it during reconnection.
◦ Race conditions in socket cleanup during ICE restart or peer disconnect/reconnect.
◦ Running out of file descriptors or improper error handling when recreating sockets.
• Diagnostics to focus on:
◦ Turn on verbose LWIP and esp_peer logging to capture socket create/close calls and any errno around select() failures.
◦ Check for code paths where sockets are closed asynchronously (e.g., in timers or callbacks) while another task still uses them.
Concrete next steps and tests (short list)
1. Enable verbose logging:
◦ esp_peer/WebRTC layers, LWIP (socket layer), and websocket/signaling. Correlate exact timestamps to see which error precedes the other.
1. Verify TURN transport capabilities:
◦ Confirm whether TCP/TLS TURN (port 443) is enabled in your esp_peer config. If skipped, enable it and test in case UDP is blocked or unstable.
1. Increase ICE/DTLS timeouts:
◦ Slightly increase ICE checks/timeouts so the constrained device has more time to complete exchange under load.
1. Reproduce with a minimal test:
◦ Run a minimal repro that only performs signaling → ICE → DTLS without starting heavy capture/encode tasks. If stable, the issue is likely resource/CPU related.
1. Watch socket lifecycle:
◦ Add logs around socket create/close calls (or enable LWIP socket tracing) to capture when a UDP socket is closed relative to the “Bad file number” message.
1. Try alternative network path:
◦ If possible, test with a different network or temporarily allow non-relay ICE to see whether the problem reproduces only under relay-only mode.
1. Share config and extra logs:
◦ If you can, share the esp_peer/ICE config snippet (which candidates/transports are enabled) and a slightly longer timestamped log around the failure. I can point to exact places to add debug logs or which options to toggle.
If you want, I can:
• Walk through the exact esp_peer configuration keys to change for enabling TCP/TLS TURN and adjusting ICE timeouts.
• Help parse an extended log (10–20 seconds before/after the failure) to identify the ordering of DTLS/ICE vs websocket errors.
Thanks again — you’ve done the right thing by isolating auth/signaling and confirming TURN candidates. If you send the esp_peer config and a timestamped extended log snippet, I’ll give precise edits to try next.
Sources: LiveKit SDK for ESP32: bringing voice AI to embedded devices | LiveKit | Firewall Tips, with interactive elementsrapid-chef-41237
03/26/2026, 3:20 AMtall-airline-16756
03/26/2026, 3:20 AMrapid-chef-41237
03/26/2026, 3:43 AMKconfig.projbuild:53, I exposed key esp_peer parameters: no_auto_reconnect, keep_role, ipv6_support, max_candidates, and alive_binding_retries.
In peer.c:39, I wired these parameters into esp_peer_default_cfg_t and added higher-signal RTC logging. At the same time, in peer.c:544, I added a filter: when ipv6_support = n, remote IPv6 candidates are discarded directly.
The default values are now: relay-only = y, no_auto_reconnect = y, keep_role = y, ipv6_support = n, max_candidates = 6, alive_binding_retries = 10, and these have been written into sdkconfig.defaults.
• I also updated the English forum post content in livekit_rtc_disconnect_forum_post.md:1.
The conclusions from this round of testing are now clearer:
There are no issues with 401, token, signaling, TLS, or TURN acquisition.
The new configuration has taken effect in practice; the serial output shows: no_auto_reconnect=1 keep_role=1 ipv6=0 max_candidates=6 alive_binding_retries=10.
Remote IPv6 candidates are explicitly discarded, but the issue still reproduces.
• The initial failure point is neither WebSocket nor “Bad file number.” It is now clear that the subscriber peer fails with CONNECT_FAILED directly during the PAIRING stage.
transport_ws: unexpected data readable on socket and UDP: Failed to select: Bad file number both occur during the teardown/reconnect phase after the RTC failure, and appear to be secondary symptoms.
• The most suspicious area at present is SDP / data channel interoperability on the subscriber side. The serial log still shows entries like Extract SDP: ... audio_mid=255 ... data_mid=255, and the subscriber answer only returns an audio m-line, without application / datachannel.
I have completed this build + flash + monitor cycle. As a next step, I suggest a minimal A/B test: disable the data channel only on the subscriber peer and check whether the transition from PAIRING → CONNECT_FAILED disappears. If you agree, I can proceed with this modification in the next iteration.tall-airline-16756
03/26/2026, 3:43 AMdatachannel capability to false in the peer config).
• Keep the publisher unchanged (still offering the datachannel).
• Rebuild/flash and capture a timestamped serial log covering the entire exchange from signaling through the PAIRING result.
2) What to watch for in the logs
• Whether the subscriber’s SDP answer still lists an application m-line or data_mid. If you removed it correctly, the answer should only include the audio m-line.
• Whether the peer state advances beyond PAIRING or immediately moves to CONNECT_FAILED.
• Any DTLS/ICE errors immediately before the CONNECT_FAILED message — note exact timestamps.
3) Additional small variants to try (if the basic A/B doesn’t fully isolate)
• Disable datachannel on both sides to see if the issue disappears entirely.
• If disabling solves it, re-enable but force the datachannel to use negotiated SCTP settings (different max channels or in-band vs out-of-band negotiation) — this helps identify whether specific datachannel parameters cause the failure.
• Try changing max_candidates down to 2 or 3 temporarily to reduce candidate churn while testing datachannel interactions (you’ve already reduced to 6; lowering further can simplify ICE behavior).
4) Potential causes if datachannel is implicated
• Incomplete or incompatible SDP attributes for application m-line (e.g., missing sctp-port, msid or mismatched data_mid) that the esp_peer subscriber can’t handle.
• A resource / timing issue when initializing SCTP/datachannel on the subscriber while also handling DTLS/Opus setup, causing a handshake timeout and immediate CONNECT_FAILED.
• A bug in datachannel transport handling in esp_peer on the subscriber path that triggers a pairing failure when application m-line is present.
5) If the A/B shows disabling datachannel fixes the failure
• Share the subscriber SDP answer (the raw SDP snippet) and the corresponding publisher offer. I can point to specific SDP lines/attributes that may be incompatible and suggest exact edits or workaround config flags to use in esp_peer.
• Also share any SCTP/datachannel-related config keys in your esp_peer config so I can recommend adjustments (e.g., negotiated channels, ordered/unordered settings, or SCTP port settings).
If you want, I can provide the exact minimal change to apply in your peer.c to stop adding application/datachannel in the subscriber answer — tell me how you currently set the datachannel capability in code (or paste the small config snippet) and I’ll give the exact edit.
Nice work so far — this is the right experimental approach. Run the A/B and paste the key 10–20s log snippet (timestamps + the SDP offer/answer) and I’ll analyze the SDP and next steps.
, with interactive elementsrapid-chef-41237
03/26/2026, 4:16 AMtall-airline-16756
03/26/2026, 4:17 AMa=setup:active|passive 值,确认 DTLS active/passive 配置是互补的。
4) 进一步缩小范围的 A/B 测试(最小改动)
• A. 维持当前 relay-only,但在 subscriber 端延后开始本地音频(或完全不启动 capture/encoder),仅执行信令+SDP+ICE/DTLS 建立,观察是否仍然失败(验证是否是资源/CPU 导致)。
• B. 在 subscriber 端尝试短暂允许非‑relay(即允许 host/srflx)以确认是否仅在 relay-only 路径下重现。若非-relay 可成功,则指向 TURN relay 特殊路径或 TURN/TCP handling 问题。
• C. 如果可行,尝试把 max_candidates 再降至 2 来减少 candidate 数量,看是否改变行为(帮助确认是否 candidate 选择或 socket 创建/关闭 race)。
5) 针对 “PAIRING 时直接 CONNECT_FAILED” 的具体日志点
• 增加日志在以下代码点(若你已经能定位到 peer state 变更处,直接在 state transition 周围):
◦ SDP answer 接收后开始处理的入口处(打印所有被解析的 ice candidates 列表与 their transport/addr/port)。
◦ DTLS 握手开始、每个阶段(ClientHello/ServerHello/Verify)与最终成功/失败回调处。
◦ SRTP key install 成功/失败处。
• 捕获任何返回的 errno 或 ioctl/select/poll 的失败值(尤其是在 socket bind/connect 或 select 调用处)。
如果这些步骤中你希望我直接给出具体代码级修改,我可以:
• 给出在 peer.c 添加更多 DTLS/ICE 状态打印的精确 patch(例如在 DTLS 错误路径打印 mbedtls 错误码或 esp_peer 返回值)。
• 或者给出一个最小的测试补丁:在 subscriber 在 PAIRING 阶段把所有 non-audio m-lines 显式忽略并且把 RTCP mux 设置为 on/off 看差异(但目前你说已经是纯 audio,所以这一步可能没必要)。
如果你愿意发:
• 一份 timestamped 的、从 SDP 交换开始到 CONNECT_FAILED 的完整串口日志(含你已增加的 RTC verbose),以及 publisher offer 与 subscriber answer 的原始 SDP,我会逐行分析并指出最可能导致失败的字段或时间点。
最后,短结论与下一步建议
• 结论:目前看起来不是 auth 或 datachannel,而更像是 subscriber 在 PAIRING 阶段某一步 DTLS/ICE/SRTP 的最终绑定或本地 socket/资源问题导致的立即失败。websocket/Bad file number 为次生现象。
• 下一步:按上面 1–5 顺序先开更高详细级别的 DTLS/ICE 日志并贴出 SDP + 关键时间窗口日志;如果你愿意我也可以直接给出需要插入的 debug log 代码片段。
你想先把完整的 SDP 与 5–10s 的详细时间窗口日志贴上来吗?或者我直接把需要添加的日志打印补丁发给你以便下次刷入?哪种方式对你更方便,我就按那方式继续。
, with interactive elementsrapid-chef-41237
03/26/2026, 4:35 AMtall-airline-16756
03/26/2026, 4:36 AMmax_candidates 降到 2,减少 candidate 数量,看是否有差异(帮助排除 candidate 绑定 race)。
5) 如果可能,收集下列额外信息
• subscriber 在 PAIRING 入口前后的 a=setup 值(active/passive)以及日志中显示的 role(controlling/controlled)。
• 是否看到任何 DTLS 错误码(例如 mbedtls 的返回码),或 ICE pair state 明确的 FAILED/CONNECTED 标志。
可选最小代码补丁(便于下一次刷入)
• 我可以给出两段你可以直接插入的日志 patch:
1) 在 DTLS 握手成功/失败处打印 mbedtls 返回码与 human-readable 描述。 2) 在 ICE candidate pairing 的每一步打印 candidate local/remote 地址与 pair 状态变更。
• 如果你愿意,我现在就把这两个短补丁(C 代码片段,能直接贴到 peer.c 的相应位置)发给你,便于下次刷入后立即获得更有意义的输出。
短结论(当前最可信假设)
• subscriber 在 PAIRING 阶段因 DTLS/ICE/SRTP 的某一步失败而立刻报 CONNECT_FAILED。websocket/Bad file number 为断开后清理引起的次生日志。下一步通过更高等级的 DTLS/ICE/SRTP 日志 + SDP 对比可以迅速定位是握手协议不兼容、role/`a=setup` mismatch、还是 socket lifecycle race。
你想先哪一步?
• 我现在把需要插入的两个最小日志补丁发给你(你可以直接贴入 peer.c 并刷入),还是你先贴出 publisher offer 与 subscriber answer 的原始 SDP 及 5–10s 串口日志片段让我分析?哪种对你更方便,我就立刻准备相应内容。, with interactive elementsrapid-chef-41237
03/26/2026, 4:53 AMtall-airline-16756
03/26/2026, 4:53 AM