Jerboa v0.2.0 Jerboa.Client

STUN client process

Use start/1 function to spawn new client process:

iex> Jerboa.Client.start server: %{address: server_ip, port: server_port}
{:ok, #PID<...>}

(see start/1 for configuration options)

Basic usage

Requesting server reflexive address

The bind/1 issues a Binding request to a server and returns reflexive IP address and port. If returned message is not a valid STUN message or it doesn’t include XOR Mapped Address attribute, the client simply crashes.

iex> Jerboa.Client.bind client_pid
{:ok, {{192, 168, 1, 20}, 32780}}

persist/1 sends a Binding indication to a server, which is not meant to return any response, but is an attempt to refresh NAT bindings in routers on the path to a server. Note that this is only an attempt, there is no guarantee that some router on the path won’t rebind client’s inside address and port.

Creating allocations

Allocation is a logical communication path between one client and multiple peers. In practice a socket is created on the server, which peers can send data to, and the server will forward this data to the client. Client can send data to the server which will forward it to one or more peers.

Refer to TURN RFC for a more detailed description.

allocate/1 is used to request an allocation on the server. On success it returns an :ok tuple, which contains allocated IP address and port number. Jerboa won’t try to request an allocation if it knows that the client already has one.

Note that allocations have an expiration time (RFC recommends 10 minutes), To refresh an existing allocation one can use refresh/1.

Installing permissions

Once the allocation is created, you may install permissions for peers in order to exchange data with them over relay. Permissions are created using create_permission/2:

create_permission client, {192, 168, 22, 111}
create_permission client, [{192, 168, 22, 111}, {212, 168, 33, 222}]

Sending and receiving data

After permission is installed, you may send and receive data from peer. To send data you must simply call send/3, providing peer’s address and data to be sent (a binary):

send client, {{192, 168, 22, 111}, 1234}, "Hello, world!"

Receiving data is handled using subscriptions mechanism. Once you subscribe to the data (using subscribe/3) sent by some peer, it will be delivered to subscribing process as a message in format:

  {:peer_data, client :: pid, peer :: address, data :: binary}

Subscriptions imply that receiving data is asynchronous by default. There is a convenience recv/2 function, which will block calling process until it receives data from the given peer address. recv accepts optional timeout in milliseconds (or atom :infinity), which defaults to 5000.

Note that permissions installed do not affect subscriptions - if you subscribe to data from peer which you’ve not installed permissions for, the data will never appear in subscribed process’ mailbox.

Logging

Client logs progress messages with :debug level, so Elixir’s Logger needs to be configured first to see them. It is recommended to allow Jerboa logging metadata, i.e. :jerboa_client and :jerboa_server:

config :logger,
  level: :debug,
  metadata: [:jerboa_client, :jerboa_server]

Summary

Functions

Creates allocation on the server or returns relayed transport address if client already has an allocation

Sends Binding request to a server

Creates permissions on the allocation for the given peer addresses

Sends Binding indication to a server

Blocks the calling process until it receives the data from the given peer

Tries to refresh the allocation on the server

Sends data to a given peer

Starts STUN client process

Stops the client

Subscribes calling process to data received from the given peer

Subscribes PID to data received from the given peer

Cancels subscription of calling process

Cancels subscription of given PID

Types

address()
address() :: {ip, port_no}
allocate_opt()
allocate_opt ::
  {:even_port, boolean} |
  {:reserve, boolean} |
  {:reservation_token, <<_::64>>}
allocate_opts()
allocate_opts() :: [allocate_opt]
error()
error ::
  :bad_response |
  :no_allocation |
  Jerboa.Format.Body.Attribute.ErrorCode.name
ip()
ip() :: :inet.ip4_address
port_no()
port_no() :: :inet.port_number
start_opt()
start_opt ::
  {:server, address} |
  {:username, String.t} |
  {:secret, String.t}
start_opts()
start_opts() :: [start_opt]
t()
t() :: pid

Functions

allocate(client, opts \\ [])
allocate(t, allocate_opts) :: {:ok, address} | {:error, error}

Creates allocation on the server or returns relayed transport address if client already has an allocation

Options

  • :even_port - optional - if set to true, EVEN-PORT attribute will be included in the request, which prompts the server to allocate even port number
  • :reserve - optional - if set to true, prompts the server to allocate an even port, reserve next highest port number, and return a reservation token which can be later used to create an allocation on reserved port. If this option is present, :even_port is ignored.
  • :reservation_token - optional - token returned by previous allocation request with reserve: true. Passing the token should result in reserved port being assigned to the allocation, or an error if the token is invalid or the reservation has timed out. If this option is present, :reserve is ignored.
bind(client)
bind(t) :: {:ok, address} | {:error, :bad_response}

Sends Binding request to a server

Returns reflexive address and port on successful response. Returns error tuple if response from the server is invalid.

create_permission(client, peers)
create_permission(t, peers :: ip | [ip, ...]) ::
  :ok |
  {:error, error}

Creates permissions on the allocation for the given peer addresses

If permission is already installed for the given address, the permission will be refreshed.

Examples

create_permission client, {192, 168, 22, 111}

create_permission client, [{192, 168, 22, 111}, {212, 168, 33, 222}]
persist(client)
persist(t) :: :ok

Sends Binding indication to a server

recv(client, peer_addr, timeout \\ 5000)
recv(t, peer_addr :: Jerboa.Client.ip, timeout :: non_neg_integer | :infinity) ::
  {:ok, peer :: Jerboa.Client.address, data :: binary} |
  {:error, :timeout}

Blocks the calling process until it receives the data from the given peer

Calling process needs to be subscribed to this peer’s data before calling this function, otherwise it will always time out.

Accepts timeout in milliseconds as optional argument (defualt is 5000), may be also atom :infinity.

This function simply uses subscriptions mechanism. It implies lack of knowledge about permissions installed for the given peer, thus if there is no permission, the function will most likely time out.

refresh(client)
refresh(t) :: :ok | {:error, error}

Tries to refresh the allocation on the server

send(client, peer, data)
send(t, peer :: address, data :: binary) ::
  :ok |
  {:error, :no_permission}

Sends data to a given peer

Note that there are no guarantees that the data sent reaches the peer. TURN servers don’t acknowledge Send indications.

Returns {:error, :no_permission} if there is no permission installed for the given peer.

start(opts)
start(options :: Keyword.t) :: Supervisor.on_start_child

Starts STUN client process

iex> opts = [server: {{192, 168, 1, 20}, 3478}, username: "user", secret: "abcd"]
iex> Jerboa.Client.start(opts)
{:ok, #PID<...>}

Options

  • :server - required - a tuple with server’s address and port
  • :username - required - username used for authentication
  • :secret - required - secret used for authentication
stop(client)
stop(t) ::
  :ok |
  {:error, error} when error: :not_found | :simple_one_for_one

Stops the client

subscribe(client, peer_addr)
subscribe(t, peer_addr :: ip) :: :ok

Subscribes calling process to data received from the given peer

Message format is

{:peer_data, client_pid :: pid, peer :: address, data :: binary}
subscribe(client, pid, peer_addr)
subscribe(t, sub :: pid, peer_addr :: ip) :: :ok

Subscribes PID to data received from the given peer

Message format is

{:peer_data, client_pid :: pid, peer :: address, data :: binary}
unsubscribe(client, peer_addr)
unsubscribe(t, peer_addr :: ip) :: :ok

Cancels subscription of calling process

unsubscribe(client, pid, peer_addr)
unsubscribe(t, sub :: pid, peer_addr :: ip) :: :ok

Cancels subscription of given PID