Adapts OTP :ssl DTLS to XGW's shared Listener socket.
Role in the stack
Erlang's SSL application talks to the network through a small callback
info (cb_info) module. This module is that adapter: inbound DTLS
datagrams are injected into the connection owner after the Listener
demuxes them, and outbound DTLS bytes go out through the same UDP (or
ICE-TCP / TURN-wrapped) path the Peer already selected.
Without this layer, OTP would open a second UDP port and break WebRTC's "one 5-tuple for STUN + DTLS + SRTP" model.
RFCs
- RFC 9147 / RFC 6347 -- DTLS datagram transport
- RFC 7983 -- sharing one 5-tuple with STUN and SRTP
- RFC 8656 -- TURN Send Indication / ChannelData when the selected path is relay
- RFC 6544 -- ICE-TCP framing when the selected path is TCP
Summary
Functions
Clears per-remote inject owner (multi-peer shared socket).
Clears TURN relay send routing.
Clears ICE-TCP send routing so send/4 uses UDP (or TURN) again.
No-op close for :ssl transport callbacks (Listener owns the UDP socket).
Sets the process that receives injected {:udp, socket, ip, port, data} messages.
Returns inet options for the underlying UDP socket.
Marks pid so later controlling_process/2 calls do not make it the DTLS UDP owner.
Delivers an inbound DTLS datagram to the current owner, or queues until one is set.
Opens a UDP socket suitable for :ssl DTLS cb_info (binary, inactive).
Peer address for :ssl callbacks.
Local UDP port from sockname/1.
Restores the DTLS UDP owner to the packet demux (or clears it) and drops pending.
Sends a DTLS datagram toward ip/port.
Binds DTLS injects from remote to the current demux/owner (or owner if given).
Routes subsequent send/4 datagrams through TURN toward peer.
Routes subsequent DTLS send/4 datagrams as ICE-TCP framed payloads on tcp_sock.
Applies inet options, ignoring :active / :deliver (Listener owns recv mode).
Local sockname via the owning Listener GenServer.
Types
@type socket() :: port()
UDP socket used as the DTLS datagram transport (:gen_udp port).
Functions
@spec clear_inject_owner( socket(), {:inet.ip_address(), :inet.port_number()} ) :: :ok
Clears per-remote inject owner (multi-peer shared socket).
@spec clear_relay_send(socket(), {:inet.ip_address(), :inet.port_number()} | nil) :: :ok
Clears TURN relay send routing.
With peer, clears only that remote on a shared Listener socket.
@spec clear_tcp_send(socket(), {:inet.ip_address(), :inet.port_number()} | nil) :: :ok
Clears ICE-TCP send routing so send/4 uses UDP (or TURN) again.
With peer, clears only that remote on a shared Listener socket.
@spec close(socket()) :: :ok
No-op close for :ssl transport callbacks (Listener owns the UDP socket).
Sets the process that receives injected {:udp, socket, ip, port, data} messages.
Flushes any packets queued before an owner was set. Ignored owners (see
ignore_owner/1) leave the previous owner unchanged.
Returns inet options for the underlying UDP socket.
@spec ignore_owner(pid()) :: :ok
Marks pid so later controlling_process/2 calls do not make it the DTLS UDP owner.
Used before :ssl.controlling_process(ssl_sock, peer) so Peer receives {:ssl, ...}
without stealing post-handshake DTLS injects needed for SCTP.
@spec inject(socket(), :inet.ip_address(), :inet.port_number(), binary()) :: :ok
Delivers an inbound DTLS datagram to the current owner, or queues until one is set.
@spec open( :inet.port_number(), keyword() ) :: {:ok, socket()} | {:error, term()}
Opens a UDP socket suitable for :ssl DTLS cb_info (binary, inactive).
When the process dictionary has {__MODULE__, :listener}, registers the
socket in :persistent_term for inject/send routing.
@spec peername(socket()) :: {:ok, {:inet.ip_address(), :inet.port_number()}}
Peer address for :ssl callbacks.
When TURN relay send is armed, returns the remote ICE peer for this association (or the singleton); otherwise a placeholder (host DTLS over shared UDP is connectionless from OTP's view).
@spec port(socket()) :: :inet.port_number()
Local UDP port from sockname/1.
@spec reset_owner(socket()) :: :ok
Restores the DTLS UDP owner to the packet demux (or clears it) and drops pending.
Keeps listener / relay_send / demux entries. Used when restarting ICE/DTLS
on a shared Listener so inbound injects still reach OTP's demux.
@spec send(socket(), :inet.ip_address(), :inet.port_number(), iodata()) :: :ok | {:error, term()}
Sends a DTLS datagram toward ip/port.
When ICE-TCP or TURN relay send is armed, forwards via framed TCP or TURN instead of a direct UDP send.
@spec set_inject_owner( socket(), {:inet.ip_address(), :inet.port_number()}, pid() | nil ) :: :ok
Binds DTLS injects from remote to the current demux/owner (or owner if given).
Used when a Peer arms DTLS for a selected ICE remote on a shared Listener socket.
@spec set_relay_send( socket(), map(), {:inet.ip_address(), :inet.port_number()}, keyword() ) :: :ok
Routes subsequent send/4 datagrams through TURN toward peer.
When channel: is set (0x4000..0x7FFF), uses ChannelData; otherwise Send Indication.
Used after ICE nominates a relay local so DTLS appears to come from the relayed
address (browser selected the relay path).
@spec set_tcp_send(socket(), port(), {:inet.ip_address(), :inet.port_number()}) :: :ok
Routes subsequent DTLS send/4 datagrams as ICE-TCP framed payloads on tcp_sock.
OTP cannot DTLS-upgrade a gen_tcp socket ({:dtls_upgrade, :notsup}); ICE-TCP
therefore reuses the UDP DTLS stack and forwards records over the nominated TCP
connection with RFC 4571 framing.
Applies inet options, ignoring :active / :deliver (Listener owns recv mode).
@spec sockname(socket()) :: {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, term()}
Local sockname via the owning Listener GenServer.