XGW.Listener (xgw v0.1.0)

Copy Markdown View Source

Shared UDP (and optional TCP) socket that receives all WebRTC traffic for many peers and routes each packet to the right XGW.Peer.

Why a shared listener

Opening one UDP port per browser would not scale and would break demux assumptions. One Listener binds the media port; inbound bytes are classified (XGW.Demux) and dispatched:

  • STUN Binding -- look up the Peer by local ICE ufrag in USERNAME (local:remote at this host)
  • RTP / RTCP / DTLS -- look up by remote {ip, port} after bind_remote/3
  • TURN ChannelData -- look up by channel number from bind_turn_channel/3
  • TURN Data Indication -- unwrap, then re-demux the inner packet

With a single registered Peer and no bind keys, all classified packets go to that Peer (Echo / one-Peer-per-Listener back-compat).

Inbound media meta includes :arrival_us (monotonic microseconds) stamped in route_packet/4 before Peer.deliver/4, so TWCC arrivals exclude Peer mailbox delay.

  • Host: :advertised_ip, :advertised_ips, :include_loopback (default true)
  • srflx: :stun_servers (default []), :gather_timeout_ms (default 3000)
  • relay: :turn_servers plus TURN credential opts
  • :relay_only -- skip host/srflx SDP validation when the answer is relay-only
  • ICE-TCP: :ice_tcp_enabled / :ice_tcp_port for passive TCP candidates

Call gather_candidates/2 before publishing answer SDP when you need host + srflx + relay lines embedded up front.

RFCs

  • RFC 7983 -- demux of STUN / DTLS / TURN ChannelData / RTP/RTCP on one socket
  • RFC 8445 -- ICE candidate gather and STUN Binding dispatch by ufrag
  • RFC 8656 -- TURN Data Indication unwrap and ChannelData
  • RFC 6544 -- optional ICE-TCP passive candidates and accept path

Summary

Functions

Allocates the next TURN channel number unique on this Listener socket.

Maps remote {ip, port} to peer_pid for RTP/RTCP/DTLS demux.

Maps TURN ChannelData channel to peer_pid.

Maps local ICE ufrag to peer_pid for STUN Binding demux.

Returns {certfile, keyfile} paths for DTLS handshakes.

Returns a specification to start this module under a supervisor.

Gathers host, srflx, and relay candidates within timeout_ms.

Host gather options configured at listener start.

Pre-answer srflx gather timeout in milliseconds.

Host ICE candidate with highest priority for the listener UDP socket.

SDP a=candidate: attribute value (without prefix) for the highest-priority host.

SDP a=candidate: lines for all gathered host candidates (without prefix).

All host ICE candidates for the listener UDP socket (multi-interface).

Returns true when ICE-TCP passive candidates are enabled.

Returns the SSL listen socket for DTLS accept (shared UDP via Transport).

SHA-256 fingerprint of the listener DTLS certificate (SDP format).

Bound port of the shared UDP socket.

Registers peer_pid for demux (monitor only until bind_* keys are set).

SDP a=candidate: lines for relay candidates gathered within the timeout.

When true, answer SDP may omit host/srflx locals (relay-only / zero-public-port).

Sends a UDP datagram on the listener socket (STUN replies, etc.).

Sends a UDP datagram and waits for :gen_udp.send/4 result (outbound media).

Sends a framed STUN message over an established ICE-TCP connection to peer_addr.

Bound socket {ip, port}.

SDP a=candidate: lines for srflx candidates gathered within the timeout.

Starts a UDP listener on ip / port (use port: 0 for an ephemeral bind).

Sends a STUN Binding request on the listener socket and waits for a response.

Configured STUN server URIs for srflx gather.

SDP a=candidate: lines for passive TCP host candidates.

Passive ICE-TCP host candidates (empty when ICE-TCP is disabled).

Connects to peer_addr over TCP if needed and sends a framed STUN message.

Returns the bound ICE-TCP listen port when enabled, else nil.

TURN credential options for relay gather.

Sends a STUN/TURN request on the listener socket and waits for a response.

Configured TURN server URIs for relay gather.

Returns the shared UDP socket used for STUN/RTP and DTLS cb_info.

Removes peer_pid and all demux keys that pointed at it.

Functions

allocate_turn_channel(listener, peer_pid)

@spec allocate_turn_channel(pid(), pid()) :: {:ok, non_neg_integer()}

Allocates the next TURN channel number unique on this Listener socket.

bind_remote(listener, peer_pid, from)

@spec bind_remote(pid(), pid(), {:inet.ip_address(), :inet.port_number()}) :: :ok

Maps remote {ip, port} to peer_pid for RTP/RTCP/DTLS demux.

bind_turn_channel(listener, peer_pid, channel)

@spec bind_turn_channel(pid(), pid(), non_neg_integer()) :: :ok

Maps TURN ChannelData channel to peer_pid.

bind_ufrag(listener, peer_pid, local_ufrag)

@spec bind_ufrag(pid(), pid(), String.t()) :: :ok

Maps local ICE ufrag to peer_pid for STUN Binding demux.

cert_paths(listener)

@spec cert_paths(pid()) :: {String.t(), String.t()}

Returns {certfile, keyfile} paths for DTLS handshakes.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

gather_candidates(listener, opts \\ [])

@spec gather_candidates(
  pid(),
  keyword()
) :: %{
  host: [XMediaLib.SDP.Candidate.t()],
  srflx: [XMediaLib.SDP.Candidate.t()],
  relay: [XMediaLib.SDP.Candidate.t()]
}

Gathers host, srflx, and relay candidates within timeout_ms.

Returns %{host: [...], srflx: [...], relay: [...]}.

gather_opts(listener)

@spec gather_opts(pid()) :: keyword()

Host gather options configured at listener start.

gather_timeout_ms(listener)

@spec gather_timeout_ms(pid()) :: non_neg_integer()

Pre-answer srflx gather timeout in milliseconds.

host_candidate(listener)

@spec host_candidate(pid()) :: XMediaLib.SDP.Candidate.t()

Host ICE candidate with highest priority for the listener UDP socket.

host_candidate_line(listener)

@spec host_candidate_line(pid()) :: String.t()

SDP a=candidate: attribute value (without prefix) for the highest-priority host.

host_candidate_lines(listener)

@spec host_candidate_lines(pid()) :: [String.t()]

SDP a=candidate: lines for all gathered host candidates (without prefix).

host_candidates(listener)

@spec host_candidates(pid()) :: [XMediaLib.SDP.Candidate.t()]

All host ICE candidates for the listener UDP socket (multi-interface).

ice_tcp_enabled?(listener)

@spec ice_tcp_enabled?(pid()) :: boolean()

Returns true when ICE-TCP passive candidates are enabled.

listen_sock(listener)

@spec listen_sock(pid()) :: :ssl.sslsocket()

Returns the SSL listen socket for DTLS accept (shared UDP via Transport).

local_fingerprint(listener)

@spec local_fingerprint(pid()) :: String.t()

SHA-256 fingerprint of the listener DTLS certificate (SDP format).

port(listener)

@spec port(pid()) :: :inet.port_number()

Bound port of the shared UDP socket.

register_peer(listener, peer_pid)

@spec register_peer(pid(), pid()) :: :ok

Registers peer_pid for demux (monitor only until bind_* keys are set).

relay_candidate_lines(listener, opts \\ [])

@spec relay_candidate_lines(
  pid(),
  keyword()
) :: [String.t()]

SDP a=candidate: lines for relay candidates gathered within the timeout.

relay_only?(listener)

@spec relay_only?(pid()) :: boolean()

When true, answer SDP may omit host/srflx locals (relay-only / zero-public-port).

send(listener, ip, port, data)

@spec send(pid(), :inet.ip_address(), :inet.port_number(), binary()) :: :ok

Sends a UDP datagram on the listener socket (STUN replies, etc.).

send_datagram(listener, ip, port, data)

@spec send_datagram(pid(), :inet.ip_address(), :inet.port_number(), binary()) ::
  :ok | {:error, term()}

Sends a UDP datagram and waits for :gen_udp.send/4 result (outbound media).

send_tcp_stun(listener, peer_addr, wire)

@spec send_tcp_stun(pid(), XGW.ICE.endpoint(), binary()) :: :ok | {:error, term()}

Sends a framed STUN message over an established ICE-TCP connection to peer_addr.

sockname(listener)

@spec sockname(pid()) :: {:inet.ip_address(), :inet.port_number()}

Bound socket {ip, port}.

srflx_candidate_lines(listener, opts \\ [])

@spec srflx_candidate_lines(
  pid(),
  keyword()
) :: [String.t()]

SDP a=candidate: lines for srflx candidates gathered within the timeout.

start_link(opts)

@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}

Starts a UDP listener on ip / port (use port: 0 for an ephemeral bind).

Optional gather opts: :advertised_ip, :advertised_ips, :include_loopback, :stun_servers, :gather_timeout_ms, :turn_servers, :relay_only, TURN credential opts, :ice_tcp_enabled, :ice_tcp_port.

stun_binding(listener, stun_ip, stun_port, timeout_ms, wire \\ nil)

@spec stun_binding(
  pid(),
  :inet.ip_address(),
  :inet.port_number(),
  non_neg_integer(),
  binary() | nil
) :: {:ok, binary()} | {:error, term()}

Sends a STUN Binding request on the listener socket and waits for a response.

Optional fifth argument is pre-encoded request wire (defaults to plain Binding).

stun_servers(listener)

@spec stun_servers(pid()) :: [String.t()]

Configured STUN server URIs for srflx gather.

tcp_candidate_lines(listener)

@spec tcp_candidate_lines(pid()) :: [String.t()]

SDP a=candidate: lines for passive TCP host candidates.

tcp_candidates(listener)

@spec tcp_candidates(pid()) :: [XMediaLib.SDP.Candidate.t()]

Passive ICE-TCP host candidates (empty when ICE-TCP is disabled).

tcp_connect_stun(listener, peer_addr, wire)

@spec tcp_connect_stun(pid(), XGW.ICE.endpoint(), binary()) :: :ok | {:error, term()}

Connects to peer_addr over TCP if needed and sends a framed STUN message.

tcp_port(listener)

@spec tcp_port(pid()) :: :inet.port_number() | nil

Returns the bound ICE-TCP listen port when enabled, else nil.

turn_credentials(listener)

@spec turn_credentials(pid()) :: keyword()

TURN credential options for relay gather.

turn_request(listener, ip, port, timeout_ms, wire)

@spec turn_request(
  pid(),
  :inet.ip_address(),
  :inet.port_number(),
  non_neg_integer(),
  binary()
) :: {:ok, binary()} | {:error, term()}

Sends a STUN/TURN request on the listener socket and waits for a response.

turn_servers(listener)

@spec turn_servers(pid()) :: [String.t()]

Configured TURN server URIs for relay gather.

udp_sock(listener)

@spec udp_sock(pid()) :: port()

Returns the shared UDP socket used for STUN/RTP and DTLS cb_info.

unregister_peer(listener, peer_pid)

@spec unregister_peer(pid(), pid()) :: :ok

Removes peer_pid and all demux keys that pointed at it.