Skip to content

feat(webrtc): true ICE-Lite controlled agent for the WebRTC-Direct listener #1512

Description

@yashksaini-coder

Follow-up on #1437. The WebRTC-Direct listener currently runs a full aioice agent in the controlled role, not a true ICE-Lite agent. It interoperates because the remote dialer is full ICE and nominates, but it is not spec-Lite.

A Lite agent (RFC 8445 §2.1 / the WebRTC-Direct server role) should:

  • gather only host candidates (we already inject a single host candidate),
  • never form a check list or send connectivity checks (respond-only),
  • always stay controlled (no role-conflict switching, no aggressive nomination),
  • skip consent-freshness as an initiator.

Today aioice has no lite mode, so the full controlled-agent machinery runs: it sends its own checks and can discover peer-reflexive candidates. Making it truly Lite means either:

  1. A controlled-agent tweak in py-libp2p — suppress our outbound checks / mark the agent respond-only after injecting the host candidate. Smaller, stays in-repo, but leans further on aioice internals.
  2. Upstream an ice_lite mode into aioice — cleaner and reusable, but out-of-repo and slower.

Plan: scope option 1 first behind the existing muxed-connection setup and verify against the go-libp2p interop suite (both directions, v1+v2). Refs #1437.

Activity

  1. yashksaini-coder commented on Sep 8, 2026

    @yashksaini-coder
    ContributorAuthor

    Findings — how other implementations handle the listener's ICE-Lite role.

    The spec requires it — webrtc-direct.md: "B acts as an ICE Lite agent … binds to a UDP port waiting for incoming STUN and SCTP packets and multiplexes based on source IP and source port."

    Every reference implementation gets Lite as a native flag in its ICE library:

    • go-libp2p (pion): settingEngine.SetLite(true) (p2p/transport/webrtc/listener.go:208, v0.49). Its whole listener setup otherwise matches ours — server DTLS role, SetICECredentials(serverUfrag, serverUfrag), UDP mux, loopback candidate, disable fingerprint verify. The one line we can't mirror is SetLite(true).
    • rust-libp2p (webrtc-rs): SettingEngine::set_lite.
    • js-libp2p: the browser client is full-ICE by construction; node side via libdatachannel.

    What pion's Lite actually does: "Lite agents do not perform connectivity checks and only provide host candidates" — host-only, respond-only (never initiate checks), always controlled, completes on the controlling peer's USE-CANDIDATE.

    Where py-libp2p stands. Our listener already meets 3 of the 4: a single injected host candidate, always controlled (ice_controlling=False), and completion on the dialer's nomination. The only gap is respond-only — we still send our own connectivity checks — because aioice has no local Lite mode, only remote_is_lite.

    Conclusion. True ICE-Lite is a library primitive everywhere else; the clean fix is to add an ice_lite mode to aioice (mirroring pion's Lite). Re-implementing it as a per-connection override of aioice internals in py-libp2p would be fragile and pinned to aioice 0.10.x. The current full-controlled agent interoperates (the dialer nominates) and is robust after #1495, so it stands as a documented interim. Leaving this open as a known ceiling; the eventual fix is upstream in aioice.

  2. yashksaini-coder commented on Sep 10, 2026

    @yashksaini-coder
    ContributorAuthor

    Update: implemented in-repo after all → #1532 (closes this).

    Revisiting my earlier "clean fix is upstream aioice" note: a contained in-repo override turned out to be clean and interop-verified. aioice's check_start is its sole sender of connectivity-check requests, so replacing it on the muxed listener connection with a respond-only version (mark the pair valid, complete on the dialer's USE-CANDIDATE) makes the agent genuinely Lite without touching aioice. Also pin switch_role so it stays controlled per RFC 8445 §6.1.1. Validated against go-libp2p v0.49 (both directions, v1+v2) — 0 outbound connectivity checks from the listener, all combos green. Upstreaming a real ice_lite into aioice is still the cleaner long-term home, but this unblocks the #1437 track now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions