Skip to content

Commit

Permalink
Merge pull request #111 from esl/even-port-and-reservations
Browse files Browse the repository at this point in the history
Even port and reservations
  • Loading branch information
erszcz committed May 12, 2017
2 parents 0c8c4a7 + ad7f7f6 commit 2c14dff
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 62 deletions.
24 changes: 22 additions & 2 deletions lib/jerboa/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ defmodule Jerboa.Client do
@type start_opt :: {:server, address}
| {:username, String.t}
| {:secret, String.t}
@type allocate_opts :: [allocate_opt]
@type allocate_opt :: {:even_port, boolean}
| {:reserve, boolean}
| {:reservation_token, <<_::64>>}
@type error :: :bad_response
| :no_allocation
| Jerboa.Format.Body.Attribute.ErrorCode.name
Expand Down Expand Up @@ -106,10 +110,26 @@ defmodule Jerboa.Client do
@doc """
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.
"""
@spec allocate(t) :: {:ok, address} | {:error, error}
def allocate(client) do
call = request(client, :allocate)
@spec allocate(t, allocate_opts) :: {:ok, address} | {:error, error}
def allocate(client, opts \\ []) do
call = request(client, {:allocate, opts})
case call.() do
{:error, :stale_nonce} -> call.()
{:error, :unauthorized} -> call.()
Expand Down
74 changes: 59 additions & 15 deletions lib/jerboa/client/protocol/allocate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,31 @@ defmodule Jerboa.Client.Protocol.Allocate do
alias Jerboa.Format.Body.Attribute.XORMappedAddress, as: XMA
alias Jerboa.Format.Body.Attribute.XORRelayedAddress, as: XRA
alias Jerboa.Format.Body.Attribute.{RequestedTransport, Lifetime, Realm,
Nonce, ErrorCode}
Nonce, ErrorCode, EvenPort,
ReservationToken}
alias Jerboa.Client
alias Jerboa.Client.Protocol
alias Jerboa.Client.Credentials

@spec request(Credentials.t) :: Protocol.request
def request(creds) do
params = params(creds)
@spec request(Credentials.t, Client.allocate_opts) :: Protocol.request
def request(creds, opts) do
params = params(creds, opts)
Protocol.encode_request(params, creds)
end

@spec eval_response(response :: Params.t, Credentials.t)
@spec eval_response(response :: Params.t, Credentials.t, Client.allocate_opts)
:: {:ok, relayed_address :: Client.address, lifetime :: non_neg_integer}
| {:ok, Client.address, non_neg_integer, reservation_token :: binary}
| {:error, Client.error, Credentials.t}
def eval_response(params, creds) do
def eval_response(params, creds, opts) do
with :allocate <- Params.get_method(params),
:success <- Params.get_class(params),
%{address: raddr, port: rport, family: :ipv4} <- Params.get_attr(params, XRA),
%XMA{} <- Params.get_attr(params, XMA),
%{duration: lifetime} <- Params.get_attr(params, Lifetime) do
%{duration: lifetime} <- Params.get_attr(params, Lifetime),
:ok <- check_reservation_token(params, opts) do
relayed_address = {raddr, rport}
{:ok, relayed_address, lifetime}
maybe_with_reservation_token(relayed_address, lifetime, params, opts)
else
:failure ->
eval_failure(params, creds)
Expand All @@ -35,13 +38,54 @@ defmodule Jerboa.Client.Protocol.Allocate do
end
end

@spec params(Credentials.t) :: Params.t
defp params(creds) do
creds
|> Protocol.base_params()
|> Params.put_class(:request)
|> Params.put_method(:allocate)
|> Params.put_attr(%RequestedTransport{})
@spec check_reservation_token(Params.t, Client.allocate_opts) :: :ok | :error
defp check_reservation_token(params, opts) do
with {:ok, true} <- Keyword.fetch(opts, :reserve),
%ReservationToken{} <- Params.get_attr(params, ReservationToken) do
:ok
else
{:ok, _} ->
:ok
:error ->
:ok
_ ->
:error
end
end

@spec maybe_with_reservation_token(Client.address, non_neg_integer, Params.t,
Client.allocate_opts)
:: {:ok, relayed_address :: Client.address, lifetime :: non_neg_integer}
| {:ok, Client.address, non_neg_integer, reservation_token :: binary}
defp maybe_with_reservation_token(relayed_address, lifetime, params, opts) do
case Keyword.fetch(opts, :reserve) do
{:ok, true} ->
%ReservationToken{value: token} = Params.get_attr(params, ReservationToken)
{:ok, relayed_address, lifetime, token}
_ ->
{:ok, relayed_address, lifetime}
end
end

@spec params(Credentials.t, Client.allocate_opts) :: Params.t
defp params(creds, opts) do
params =
creds
|> Protocol.base_params()
|> Params.put_class(:request)
|> Params.put_method(:allocate)
|> Params.put_attr(%RequestedTransport{})
cond do
opts[:reservation_token] ->
token = opts[:reservation_token]
params |> Params.put_attr(%ReservationToken{value: token})
opts[:reserve] == true ->
params |> Params.put_attr(%EvenPort{reserved?: true})
opts[:even_port] == true ->
params |> Params.put_attr(%EvenPort{reserved?: false})
true ->
params
end
end

@spec eval_failure(resp :: Params.t, Credentials.t)
Expand Down
62 changes: 44 additions & 18 deletions lib/jerboa/client/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ defmodule Jerboa.Client.Worker do
transaction = Transaction.new(from, id, binding_response_handler())
{:noreply, add_transaction(state, transaction)}
end
def handle_call(:allocate, from, state) do
def handle_call({:allocate, opts}, from, state) do
if state.relay.address do
_ = Logger.debug "Allocation already present on the server, " <>
"allocate request blocked"
{:reply, {:ok, state.relay.address}, state}
else
{id, request} = Allocate.request(state.credentials)
{id, request} = Allocate.request(state.credentials, opts)
_ = Logger.debug "Sending allocate request to the server"
send(request, state.server, state.socket)
transaction = Transaction.new(from, id, allocate_response_handler())
transaction = Transaction.new(from, id, allocate_response_handler(opts))
{:noreply, add_transaction(state, transaction)}
end
end
Expand Down Expand Up @@ -184,7 +184,7 @@ defmodule Jerboa.Client.Worker do
%{state | credentials: creds, relay: relay}
end

@spec setup_logger_metadata(UDP.socket, Client.address) :: any
@spec setup_logger_metadata(UDP.socket, Client.address) :: :ok
defp setup_logger_metadata(socket, server) do
{:ok, port} = :inet.port(socket)
metadata = [jerboa_client: "#{inspect self()}:#{port}",
Expand Down Expand Up @@ -274,22 +274,15 @@ defmodule Jerboa.Client.Worker do
end
end

@spec allocate_response_handler :: Transaction.handler
defp allocate_response_handler do
@spec allocate_response_handler(Client.allocate_opts) :: Transaction.handler
defp allocate_response_handler(opts) do
fn params, creds, relay ->
case Allocate.eval_response(params, creds) do
case Allocate.eval_response(params, creds, opts) do
{:ok, relayed_address, lifetime, reservation_token} ->
handle_allocate_success_with_token(relayed_address, lifetime,
reservation_token, relay, creds)
{:ok, relayed_address, lifetime} ->
_ = Logger.debug fn ->
"Received success allocate reponse, relayed address: " <>
Client.format_address(relayed_address)
end
new_relay =
relay
|> Map.put(:address, relayed_address)
|> Map.put(:lifetime, lifetime)
|> update_allocation_timer()
reply = {:ok, relayed_address}
{reply, creds, new_relay}
handle_allocate_success(relayed_address, lifetime, relay, creds)
{:error, reason, new_creds} ->
_ = Logger.debug fn ->
"Error when receiving allocate response, reason: #{inspect reason}"
Expand All @@ -300,6 +293,39 @@ defmodule Jerboa.Client.Worker do
end
end

@spec handle_allocate_success_with_token(Client.address, non_neg_integer,
binary, Relay.t, Credentials.t) :: {term, Credentials.t, Relay.t}
defp handle_allocate_success_with_token(relayed_address, lifetime,
reservation_token, relay, creds) do
_ = Logger.debug fn ->
"Received success allocate reponse with reservation token, " <>
"relayed address: " <> Client.format_address(relayed_address)
end
new_relay = init_new_relay(relay, relayed_address, lifetime)
reply = {:ok, relayed_address, reservation_token}
{reply, creds, new_relay}
end

@spec handle_allocate_success(Client.address, non_neg_integer, Relay.t,
Credentials.t) :: {term, Credentials.t, Relay.t}
defp handle_allocate_success(relayed_address, lifetime,
relay, creds) do
_ = Logger.debug fn ->
"Received success allocate reponse, relayed address: " <>
Client.format_address(relayed_address)
end
new_relay = init_new_relay(relay, relayed_address, lifetime)
reply = {:ok, relayed_address}
{reply, creds, new_relay}
end

defp init_new_relay(old_relay, relayed_address, lifetime) do
old_relay
|> Map.put(:address, relayed_address)
|> Map.put(:lifetime, lifetime)
|> update_allocation_timer()
end

@spec refresh_response_handler :: Transaction.handler
defp refresh_response_handler do
fn params, creds, relay ->
Expand Down
Loading

0 comments on commit 2c14dff

Please sign in to comment.