-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simple TURN client #67
Conversation
lib/jerboa/client.ex
Outdated
def bind(client) do | ||
GenServer.call(client, :bind) | ||
GenServer.call(client, :bind, 2 * timeout()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 * timeout()
doesn't convey any idea "why" you're doing that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I don't know why it it 2*
. What do you recommend, leave timeout()
or go with the default GenServer timeout? If we go with the default, it doesn't make much sense to configure timeout at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there's no reason at all with changing the timeouts I would get rid of it entirrely.
lib/jerboa/client/protocol.ex
Outdated
@moduledoc false | ||
|
||
## Pure functions which construct requests or indications | ||
## and processes responses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process
not processes
lib/jerboa/client/protocol.ex
Outdated
{:ok, new_state} -> | ||
{{:ok, new_state.mapped_address}, empty_transaction(new_state)} | ||
error -> | ||
{error, state} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong alignment
end | ||
|
||
@spec eval_bind_resp(Worker.state) | ||
:: {{:ok, mapped_address :: Client.address}, Worker.state} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that the :ok
or :error
are in the inner tuple - is there any reason for that?
This results in the following code: {{:error, :bad_response}, worker_state} = Protocol.eval_bind_resp(..)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the inner tuple is the result returned to the caller of Jerboa.Client.bind/allocate/refresh..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to pass up what was returned from the Jerboa.Client.bind/allocate/refresh..
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, when someone calls Jerboa.Client.bind
it will internally call Protocol.eval_bind_resp
, which returns a two elemenet tuple (from the Client module point of view): {result, new_state}
- this way we just extract these two values, and return {:reply, result, new_state}
from the handle_call/3
callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
lib/jerboa/client/protocol.ex
Outdated
|> Params.put_attr(%Nonce{value: state.nonce}) | ||
end | ||
|
||
require Logger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
require
in the middle of a file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing line from debugging, going to remove that.
|> Protocol.eval_bind_resp() | ||
{:reply, result, new_state} | ||
end | ||
def handle_call(:allocate, _, state) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmhm that function could be better.. to many levels..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, probably with
would make this prettier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately no, I can't solve this using with
, because I won't be able to return new_state
from one of the else clauses. To be honest, I have no idea how to refactor it at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if state.relayed_address do
{:reply, {:ok, state.relayed_address}, state}
else
{result, new_state} = request_allocation(state)
{:reply, result, new_state}
end
why not that way and updating the timer in the request_allocation/1
.
Another alternative I can spot is:
if state.relayed_address do
{:reply, {:ok, state.relayed_address}, state}
else
{result, new_state} = request_allocation(state)
{:reply, result, maybe_update_lifetime_timer(result, new_state)}
end
lib/jerboa/client/worker.ex
Outdated
socket = socket(state) | ||
{address, port} = server(state) | ||
:ok = UDP.send(socket, address, port, req) | ||
{:ok, {^address, ^port, response}} = UDP.recv(socket, 0, timeout()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everytime you need timeout you take it our from the App env? no no no, that's not that good idea.. why not putting it into the server's state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have thought about it too. Is it that much overhead of calling application's env? IIRC these are just ETS tables. I know that when we issue many such calls it'd become the problem. I'm gonna fix that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true - it shouldn't be that bad.
lib/jerboa/client/worker.ex
Outdated
|> Protocol.eval_allocate_resp() | ||
case result do | ||
:retry -> | ||
request_allocation(new_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Infinite loop if we fail to perform an allocation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about introducing a retries counter? So that we fail after a certain number of tries. The call to client process would probably fail due to a timeout, but this will ensure that process doesn't get stuck in this loop.
Another reasonable solution would be to fail just after one retry. We retry a request when we get initial unauthorized
response, which is completely fine, but subsequent requests shouldn't result in such response.
We also retry when the response is stale_nonce
, in which case the next request should succeed. If it doesn't there may be two reasons:
- the server is dumb/misconfigured/non-compliant with RFC, and just returns stale_nonce because something bad happens - in this case it is totally fine to not retry
- the server has really small nonce expiration time, and due to network conditions our request arrived after the new nonce has expired - I don't think it is likely to happen (it wouldn't be reasonable from the server side) but we can't assume it won't happen at some point. In this case a next retry would be justified, but we can just return
stale_nonce
error to the caller and assume that he'll retry manually.
What do you think is the most reasonable solution here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we need to distinguish situations when we want to retry because there's no response (cases when we suspect network failure) from the cases where according to the protocol we could retry.
In the first case the counter looks ok to me (providing the retires are at least logged).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think logging is what we should add to the client before even proceeding with next features.
lib/jerboa/client/worker.ex
Outdated
|> Protocol.eval_refresh_resp() | ||
case result do | ||
:retry -> | ||
request_refresh(new_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initinite loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same comment as above applies, but here we only retry on stale_nonce
.
Yes, that shouldn't be a problem. I don't think the client code will change at all |
Use Params API, disable timeouts on indications, use Format.decode! to crash on undecodable messages, store mapped address in client's state.
Until we run Jerboa in real world, we can't assume what timeout is sufficient.
Default retries count for requests is 1.
e59e5aa
to
fb16414
Compare
I think that last thing we need here is some kind of debug logging, and after that we can merge. It has already became too big. |
I spotted a bug, need a bit more work on that. |
Ebert has finished reviewing this Pull Request and has found:
But beware that this branch is 4 commits behind the You can see more details about this review at https://ebertapp.io/github/esl/jerboa/pulls/67. |
Alright, now everything is fine. Please merge it if you don't see any more issues 🙂 |
Still waiting for @mentels to click "Approve" :) |
Extended client process to issue allocate and refresh requests and react to responses. It also includes retries when stale nonce or unathorized errors are returned from the server. Also moved STUN/TURN processing to
Jerboa.Client.Protocol
module.