# estun
[](https://github.com/benoitc/estun/actions/workflows/ci.yml)
[](https://hex.pm/packages/estun)
Modern Erlang STUN client library for OTP 28+.
Pure Erlang implementation using the `socket` module. Supports RFC 5389 (STUN), RFC 5769 (test vectors), and RFC 5780 (NAT behavior discovery).
## Features
- STUN binding requests and responses
- NAT type detection (mapping and filtering behavior)
- UDP hole punching with keepalive
- Socket transfer for P2P traffic
- Short-term and long-term authentication
- IPv4 and IPv6 support
## Requirements
- Erlang/OTP 28 or later
## Installation
Add to your `rebar.config`:
```erlang
{deps, [
{estun, "0.1.0"}
]}.
```
Or from git:
```erlang
{deps, [
{estun, {git, "https://github.com/benoitc/estun.git", {tag, "v0.1.0"}}}
]}.
```
## Quick Start
```erlang
%% 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
```erlang
%% 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
```erlang
%% 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
```erlang
%% 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
```erlang
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
```erlang
%% 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/](examples/simple_p2p/) for a runnable test.
### Docker P2P (Cross-Subnet)
```bash
cd examples/docker_p2p && ./run.sh
```
Creates two isolated Docker networks and demonstrates P2P communication between them.
See [examples/docker_p2p/](examples/docker_p2p/) for details.
## Documentation
- [API Reference](docs/api/estun.md)
- [Getting Started](docs/getting-started/quickstart.md)
- [Hole Punching Guide](docs/guide/hole-punching.md)
- [NAT Discovery](docs/guide/nat-discovery.md)
## License
MIT License - see [LICENSE](LICENSE) for details.