An Elixir WebRTC gateway: ICE, DTLS, and SRTP on the wire; plaintext RTP/RTCP (and optional data channels) for your app.

Ported and updated from an earlier Xirsys media gateway implementation; released for public use.

If you have used RTCPeerConnection in the browser, you already know the hard parts -- finding a path (ICE), securing it (DTLS), encrypting media (SRTP). XGW is the server-side half of that peer connection. Your product still owns signaling (offer/answer over WebSocket or similar). XGW takes over once you have SDP.

Home: https://github.com/Lazarus404/xgw

How the pieces fit together (friendly walkthrough): ARCHITECTURE.md.

What problem this solves

Browsers speak encrypted WebRTC. Your media server wants clear packets so it can mix, forward, or inspect them.

XGW sits in the middle:

Browser  -- ICE + DTLS + SRTP -->  XGW  -- plaintext RTP/RTCP -->  your app

Typical hosts are SFUs or media servers -- the same product role as Janus, mediasoup, or LiveKit: they own rooms and signaling, then call into XGW.Peer. Under the hood XGW leans on xsockets for listeners/sockets and xmedialib for STUN/RTP/SRTP/SDP helpers.

Mental model (JS -> XGW)

You already knowRough XGW match
One RTCPeerConnectionOne XGW.Peer process
setRemoteDescription / answerXGW.Peer.apply_descriptions/4
addIceCandidate / onicecandidateadd_remote_candidate/2 and {:xgw, :local_candidate, ...}
Media after connected{:xgw, :rtp, ...} in / SendHandle out
Data channelsXGW.Peer.Datachannel + handler messages

Listener = shared media port. Peer = one remote browser on that port.

What you get

  • Shared UDP listener with multi-peer demux (STUN / DTLS / RTP on one socket)
  • ICE-lite by default; optional ICE-full (checks, nomination, srflx/relay gather)
  • Trickle ICE both ways; consent freshness so dead paths close cleanly
  • DTLS-SRTP handshake, fingerprint check, per-SSRC encrypt/decrypt
  • Hot-path outbound RTP via Peer.SendHandle (skip GenServer when you can)
  • Optional SCTP data channels over DTLS
  • Optional ICE-TCP when UDP is blocked
  • SDP helpers that turn offer/answer into transport config
  • Browser spike harness: mix xgw.spike

Main modules

You will touch these first:

ModuleJob in plain English
XGW.ListenerShared UDP (+ optional TCP) port; classify packets; route to Peers
XGW.PeerOne remote client: ICE -> DTLS -> SRTP -> events to your handler
XGW.DescriptionsParse SDP offer/answer into ICE creds, fingerprints, SSRC maps
XGW.Demux"Is this STUN, DTLS, or media?" from the first byte
XGW.ICE / Lite / FullFind a working network path
XGW.DTLSSecure the path and export SRTP keys
XGW.SRTPSessionEncrypt outbound / decrypt inbound media
XGW.Peer.SendHandleFast outbound RTP after DTLS is up
XGW.Peer.DatachannelOptional data-channel child (keeps Peer free for media)

Full module map and call lifecycle: ARCHITECTURE.md.

ICE-lite vs ICE-full (quick pick)

ICE-lite (default): the browser does most connectivity-check work; XGW answers STUN Bindings and tracks consent. Simple. Fine when the server address is stable and well advertised.

ICE-full (ice_lite: false): XGW runs the full checklist -- pairs, checks, nomination -- and can gather srflx/relay candidates (and trickle them to the browser). Use this for tougher NATs, relay-heavy deploys, or when you need ICE-TCP.

Either mode emits familiar handler messages like {:xgw, :ice_connected, peer} and {:xgw, :dtls_connected, peer} when setup succeeds.

RFCs (the specs we implement)

You do not need to read these to run tests. They are here when you want the official wording:

Dependencies

Local checkouts before remotes catch up:

mix deps.get
mix test

Browser spike

Want to see a real Chrome/Firefox peer talk to XGW without wiring a full SFU?

mix xgw.spike --port 10000 --http-port 8080

Open the printed URL, allow mic if asked, and watch ICE/DTLS/RTP come up. Steps and outcome notes: test/spike/README.md.

ICE-TCP variant:

mix xgw.spike --ice-tcp

Tests

Default suite (unit + integration, no live browser):

mix test

Spike tests under test/spike/ are tagged :external and excluded by default.

  • ARCHITECTURE.md -- layers, lifecycle, demux, module map
  • test/spike/README.md -- manual browser interop steps