STUN and TURN library for Erlang / Elixir http://www.ejabberd.im
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Holger Weiss f84ef19441 Don't complain about 'proxy_protocol' option 1 month ago
.github/workflows Actions: test default compilation, that is with rebar2 7 months ago
include Update copyright year to 2022 5 months ago
src Don't complain about 'proxy_protocol' option 1 month ago
.gitignore Let Git ignore EDoc output 7 months ago
CHANGELOG.md Release 1.2.2 2 months ago
CODE_OF_CONDUCT.md Add Contribution doc and CoC 3 years ago
CONTRIBUTING.md Add Contribution doc and CoC 3 years ago
LICENSE.txt License changed to Apache v2 7 years ago
Makefile Make parameterizable the rebar binary 1 year ago
README.md Switch from Travis to Github Actions 1 year ago
rebar.config Release 1.2.2 2 months ago
rebar.config.script Update copyright year to 2022 5 months ago

README.md

STUN

CI Coverage Status Hex version

STUN and TURN library for Erlang / Elixir.

Both STUN (Session Traversal Utilities for NAT) and TURN standards are used as technics to establish media connection between peers for VoIP (for example using SIP or Jingle) and WebRTC.

They are part of a more general negotiation technique know as ICE (Interactive Connectivity Establishment).

To summarize:

  • A STUN server is used to get an external network address. It does not serve as a relay for the media traffic.
  • TURN servers are used to relay traffic if direct (peer to peer) connection fails.

Build

This is a pure Erlang implementation, so you do not need to have specific C libraries installed for the STUN, TURN, ICE code.

However, this code depends on ProcessOne Fast TLS, which depends on OpenSSL 1.0.0+ library.

Generic build

You can trigger build with:

make

Usage

The following sequence describe a STUN establishment.

First, start the application and stun listener at 127.0.0.1:

1> application:start(stun).
ok
2> stun_listener:add_listener({127, 0, 0, 1}, 3478, udp, []).
ok

Then, you can form and send a BindRequest:

3> rr(stun).
[state,stun,turn]
4> random:seed(erlang:timestamp()).
undefined

You can form a transaction id. Should be always 96 bit:

5> TrID = random:uniform(1 bsl 96).
41809861624941132369239212033

You then create a BindRequest message.

16#001 is ?STUN_METHOD_BINDING, defined in include/stun.hrl

6> Msg = #stun{method = 16#001, class = request, trid = TrID}.
#stun{class = request,method = 1,magic = 554869826,
     trid = 41809861624941132369239212033,raw = <<>>,
     unsupported = [],'ALTERNATE-SERVER' = undefined,
     'CHANNEL-NUMBER' = undefined,'DATA' = undefined,
     'DONT-FRAGMENT' = false,'ERROR-CODE' = undefined,
     'LIFETIME' = undefined,'MAPPED-ADDRESS' = undefined,
     'MESSAGE-INTEGRITY' = undefined,'NONCE' = undefined,
     'REALM' = undefined,'REQUESTED-TRANSPORT' = undefined,
     'SOFTWARE' = undefined,'UNKNOWN-ATTRIBUTES' = [],
     'USERNAME' = undefined,'XOR-MAPPED-ADDRESS' = undefined,
     'XOR-PEER-ADDRESS' = [],'XOR-RELAYED-ADDRESS' = undefined}

You can then establish connection to running server:

7> {ok, Socket} = gen_udp:open(0, [binary, {ip,
7> {127,0,0,1}},{active,false}]).
{ok,#Port<0.1020>}
8> {ok, Addr} = inet:sockname(Socket).
{ok,{{127,0,0,1},41906}}

The following call is for encoding BindRequest:

9> PktOut = stun_codec:encode(Msg).
<<0,1,0,0,33,18,164,66,135,24,78,148,65,4,128,0,0,0,0,1>>

The BindRequest can then be send:

10> gen_udp:send(Socket, {127,0,0,1}, 3478, PktOut).
ok

The follow code receives the BindResponse:

11> {ok, {_, _, PktIn}} = gen_udp:recv(Socket, 0).
{ok,{{127,0,0,1},
    3478,
    <<1,1,0,32,33,18,164,66,135,24,78,148,65,4,128,0,0,0,0,
      1,128,34,0,15,...>>}}

You can then decode the BindResponse:

12> {ok, Response} = stun_codec:decode(PktIn, datagram).
{ok,#stun{class = response,method = 1,magic = 554869826,
         trid = 41809861624941132369239212033,raw = <<>>,
         unsupported = [],'ALTERNATE-SERVER' = undefined,
         'CHANNEL-NUMBER' = undefined,'DATA' = undefined,
         'DONT-FRAGMENT' = false,'ERROR-CODE' = undefined,
         'LIFETIME' = undefined,'MAPPED-ADDRESS' = undefined,
         'MESSAGE-INTEGRITY' = undefined,'NONCE' = undefined,
         'REALM' = undefined,'REQUESTED-TRANSPORT' = undefined,
         'SOFTWARE' = <<"P1 STUN library">>,
         'UNKNOWN-ATTRIBUTES' = [],'USERNAME' = undefined,
         'XOR-MAPPED-ADDRESS' = {{127,0,0,1},41906},
         'XOR-PEER-ADDRESS' = [],'XOR-RELAYED-ADDRESS' = undefined}}

Finally, checking 'XOR-MAPPED-ADDRESS' attribute, should be equal to locally binded address:

13> Addr == Response#stun.'XOR-MAPPED-ADDRESS'.
true

Development

Test

Unit test

You can run eunit test with the command:

make test

References

You can refer to IETF specifications to learn more:

  • RFC 5389: Session Traversal Utilities for NAT (STUN).
  • RFC 5766: Traversal Using Relays around NAT (TURN): Relay Extensions to STUN.
  • RFC 5245: Interactive Connectivity Establishment (ICE): A Protocol for NAT Traversal for Offer/Answer Protocols.
  • RFC 6544: TCP Candidates with Interactive Connectivity Establishment (ICE)