XGW.DTLS.Transport (xgw v0.1.0)

Copy Markdown View Source

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

Types

UDP socket used as the DTLS datagram transport (:gen_udp port).

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

socket()

@type socket() :: port()

UDP socket used as the DTLS datagram transport (:gen_udp port).

Functions

clear_inject_owner(socket, remote)

@spec clear_inject_owner(
  socket(),
  {:inet.ip_address(), :inet.port_number()}
) :: :ok

Clears per-remote inject owner (multi-peer shared socket).

clear_relay_send(socket, peer \\ nil)

@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.

clear_tcp_send(socket, peer \\ nil)

@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.

close(socket)

@spec close(socket()) :: :ok

No-op close for :ssl transport callbacks (Listener owns the UDP socket).

controlling_process(socket, pid)

@spec controlling_process(socket(), pid()) :: :ok

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.

getopts(socket, opts)

@spec getopts(socket(), [atom()]) :: {:ok, keyword()} | {:error, term()}

Returns inet options for the underlying UDP socket.

ignore_owner(pid)

@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.

inject(socket, ip, port, data)

@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.

open(port, opts)

@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.

peername(socket)

@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).

port(socket)

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

Local UDP port from sockname/1.

reset_owner(socket)

@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.

send(socket, ip, port, data)

@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.

set_inject_owner(socket, remote, owner \\ nil)

@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.

set_relay_send(socket, allocation, peer, opts \\ [])

@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).

set_tcp_send(socket, tcp_sock, peer)

@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.

setopts(socket, opts)

@spec setopts(
  socket(),
  keyword()
) :: :ok | {:error, term()}

Applies inet options, ignoring :active / :deliver (Listener owns recv mode).

sockname(socket)

@spec sockname(socket()) ::
  {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, term()}

Local sockname via the owning Listener GenServer.