Skip to content
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

TURN server timeout raises an exception #78

Open
ozobotnovako opened this issue Jan 17, 2024 · 0 comments
Open

TURN server timeout raises an exception #78

ozobotnovako opened this issue Jan 17, 2024 · 0 comments

Comments

@ozobotnovako
Copy link

Description

When using a TURN server that cannot be reached, Connection.gather_candidates() raises an exception. That makes the gathering failed even if the local candidates and/or STUN candidates were gathered successfully. Unreachable STUN server timeouts as expected.

This is problematic when the server goes down, or in case the local firewall is wrongly configured.

Versions

aioice==0.9.0
Python 3.11.1

To reproduce

Run the following program and wait for 60 seconds.

import asyncio
import aioice


async def main():
    connection = aioice.Connection(
        ice_controlling=False,
        turn_server=("example.com", 12345),
        turn_username="username",
        turn_password="password",
        turn_transport="udp",
    )

    await connection.gather_candidates()

asyncio.run(main())

The same problem arises with turn_transport="tcp", just the timeout is longer (around 250s for me).

Another way of reproducing the issue is to use a working TURN server and block the outgoing traffic locally, e.g. iptables -A OUTPUT -p udp --dport 12345 -j REJECT.

Behavior

An exception is raised after 60 seconds

Traceback (most recent call last):
  File "/home/novako/playground/webrtc/test_aioice_timeouts.py", line 21, in <module>
    asyncio.run(main())
  File "/home/novako/.pyenv/versions/3.11.1/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/home/novako/.pyenv/versions/3.11.1/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/.pyenv/versions/3.11.1/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/novako/playground/webrtc/test_aioice_timeouts.py", line 17, in main
    await connection.gather_candidates()
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/ice.py", line 454, in gather_candidates
    for candidates in await asyncio.gather(*coros):
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/ice.py", line 940, in get_component_candidates
    _, protocol = await turn.create_turn_endpoint(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/turn.py", line 441, in create_turn_endpoint
    await turn_transport._connect()
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/turn.py", line 390, in _connect
    self.__relayed_address = await self.__inner_protocol.connect()
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/turn.py", line 123, in connect
    response, _ = await self.request_with_retry(request)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/turn.py", line 243, in request_with_retry
    response, addr = await self.request(request)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/turn.py", line 230, in request
    return await transaction.run()
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/novako/playground/venv/lib/python3.11/site-packages/aioice/stun.py", line 302, in run
    return await self.__future
           ^^^^^^^^^^^^^^^^^^^
aioice.stun.TransactionTimeout: STUN transaction timed out

Expected behavior

The function does not raise an exception, it respects the timeout parameter and returns host and STUN candidates.

Analysis

Connection.gather_candidates() calls Connection.get_component_candidates() with default timeout=5 parameter. The latter method then does three things. First, it gathers host candidates, then it gathers STUN candidates, waiting for the results by asyncio.wait() using the timeout parameter, and finally it gathers TURN candidates. However, the timeout is not respected here, making the await turn.create_turn_endpoint() call block until an internal socket timeout is reached.

Proposed solution

With my limited knowledge of the library, I suggest using asyncio.wait() when creating the endpoint and only adding the candidates if the query has been successful. In worst case, when both STUN and TURN is unavailable, this would make the real time before the function times out double, so joining the both queries under a single asyncio.wait() may be in place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant