estun

CIHex.pm

Modern Erlang STUN client library for OTP 27+.

Pure Erlang implementation using the socket module. Supports RFC 5389 (STUN), RFC 5769 (test vectors), and RFC 5780 (NAT behavior discovery).

Features

Requirements

Installation

Add to your rebar.config:

{deps, [
    {estun, "0.1.0"}
]}.

Or from git:

{deps, [
    {estun, {git, "https://github.com/benoitc/estun.git", {tag, "v0.1.0"}}}
]}.

Quick Start

%% Add a STUN server
{ok, ServerId} = estun:add_server(#{host => "stun.l.google.com", port => 19302}).

%% Discover public address
{ok, MappedAddr} = estun:discover().

Hole Punching

%% Open a socket and bind it
{ok, SocketRef} = estun:open_socket().
{ok, MappedAddr} = estun:bind_socket(SocketRef, ServerId).

%% Start keepalive to maintain NAT binding
ok = estun:start_keepalive(SocketRef, 25).

%% Punch through to peer
{ok, connected} = estun:punch(SocketRef, PeerIP, PeerPort).

%% Transfer socket for direct P2P communication
{ok, Socket, MappedAddr} = estun:transfer_socket(SocketRef).

NAT Discovery

%% Discover NAT behavior (requires RFC 5780 compliant server)
{ok, Behavior} = estun:discover_nat(ServerId).

%% Returns #nat_behavior{} with:
%%   mapping_behavior  - endpoint_independent | address_dependent | address_port_dependent
%%   filtering_behavior - endpoint_independent | address_dependent | address_port_dependent
%%   nat_present       - true | false
%%   hairpin_supported - true | false | unknown

Event Handling

%% Set event handler for binding lifecycle
estun:set_event_handler(SocketRef, self()).

%% Receive events
receive
    {estun_event, SocketRef, {binding_created, Addr}} -> ok;
    {estun_event, SocketRef, {binding_refreshed, Addr}} -> ok;
    {estun_event, SocketRef, {binding_changed, OldAddr, NewAddr}} -> ok;
    {estun_event, SocketRef, {binding_expiring, RemainingMs}} -> ok;
    {estun_event, SocketRef, {binding_expired}} -> ok
end.

Server Configuration

estun:add_server(#{
    host => "stun.example.com",
    port => 3478,                    %% default
    transport => udp,                %% udp | tcp | tls
    family => inet,                  %% inet | inet6
    auth => none,                    %% none | short_term | long_term
    username => <<"user">>,
    password => <<"pass">>,
    realm => <<"example.com">>
}).

Examples

Simple P2P

%% Discover public address
{ok, SocketRef} = estun:open_socket().
{ok, MyAddr} = estun:bind_socket(SocketRef, default).
io:format("Public: ~p:~p~n", [MyAddr#stun_addr.address, MyAddr#stun_addr.port]).

%% Exchange MyAddr with peer, then connect
{ok, connected} = estun:punch(SocketRef, PeerIP, PeerPort).

%% Transfer socket for direct communication
{ok, Socket, _} = estun:transfer_socket(SocketRef).
socket:sendto(Socket, <<"Hello!">>, #{family => inet, addr => PeerIP, port => PeerPort}).

See examples/simple_p2p/ for a runnable example.

Docker P2P (Cross-Subnet)

cd examples/docker_p2p && ./run.sh

Creates two isolated Docker networks and demonstrates P2P communication between them. See examples/docker_p2p/ for details.

Documentation

Full documentation is available at benoitc.github.io/estun.

License

MIT License - see LICENSE for details.