Giter Club home page Giter Club logo

aioice's Introduction

aioice

rtd pypi-v pypi-pyversions pypi-l pypi-wheel tests codecov

What is aioice?

aioice is a library for Interactive Connectivity Establishment (RFC 5245) in Python. It is built on top of asyncio, Python's standard asynchronous I/O framework.

Interactive Connectivity Establishment (ICE) is useful for applications that establish peer-to-peer UDP data streams, as it facilitates NAT traversal. Typical usecases include SIP and WebRTC.

To learn more about aioice please read the documentation.

Example

#!/usr/bin/env python

import asyncio
import aioice

async def connect_using_ice():
    connection = aioice.Connection(ice_controlling=True)

    # gather local candidates
    await connection.gather_candidates()

    # send your information to the remote party using your signaling method
    send_local_info(
        connection.local_candidates,
        connection.local_username,
        connection.local_password)

    # receive remote information using your signaling method
    remote_candidates, remote_username, remote_password = get_remote_info()

    # perform ICE handshake
    for candidate in remote_candidates:
        await connection.add_remote_candidate(candidate)
    await connection.add_remote_candidate(None)
    connection.remote_username = remote_username
    connection.remote_password = remote_password
    await connection.connect()

    # send and receive data
    await connection.sendto(b'1234', 1)
    data, component = await connection.recvfrom()

    # close connection
    await connection.close()

asyncio.get_event_loop().run_until_complete(connect_using_ice())

License

aioice is released under the BSD license.

aioice's People

Contributors

buendiya avatar eerimoq avatar frankkkkk avatar jlaine avatar kennytheeggman avatar khancyr avatar marksteward avatar saiongole avatar sebastianriese avatar xobs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

aioice's Issues

Short mdns resolve timeout causing WebRTC connection failure on local network

Hi,

I am facing a problem where the frontend, using JavaScript, connects to a Python aiortc WebRTC server. The connection works fine with a STUN server, but fails when no internet access is provided. The issue is similar to the one described in the following GitHub issue: Webcam example without Internet connection.

Upon investigation, I discovered that the RtcIceCandidate is in mdns format and uses mdns to resolve the hostname. The ice.py code snippet below illustrates this:

if mdns.is_mdns_hostname(remote_candidate.host):
    mdns_protocol = await get_or_create_mdns_protocol(self)
    remote_addr = await mdns_protocol.resolve(remote_candidate.host)
    if remote_addr is None:

The resolve timeout in the mdns protocol uses a default value of one second. However, on my computer, I have several network adapters (at least 4, including virtual networks such as VirtualBox and VMware) that can cause the resolution to take more than one second. I solved the problem by changing the timeout to 5 seconds.

I would like to know if there is a way to customize the resolve timeout in aioice so that it can be based on my situation to use the appropriate timeout.

Thank you.

Multiple early checks from same source results in unhandled tasks

Tested on version 0.7.6.

If multiple checks from a remote peer arrive before _check_list from Connection class is set, then all of them are appended to the _early_checks list and then acted upon on the connect method:

        # handle early checks
        for early_check in self._early_checks:
            self.check_incoming(*early_check)
        self._early_checks = []

Then the check_incoming method creates a task for each early check and overwrites the handle for the associated pair:

            pair.handle = asyncio.ensure_future(self.check_start(pair))

Since the handle for some tasks is lost, they become unreachable and are not canceled after completion.

It is even possible to see mutiple times a transition State.IN_PROGRESS -> State.IN_PROGRESS for some candidate pairs.

netifaces is unmaintained

I can't use aiortc on 64-bit Windows because there's no wheel for netifaces and the project has been archived.

Happy to address this, but would it be better to fix netifaces or remove it as a dependency?

TURN server timeout raises an exception

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.

Library fails with fake network devices

I'm running aiortc under Ubuntu 16.04 on WSL on Windows 10, and it has the following network interfaces:

(python) xobs@Cuboid:~$ ip -4 addr
13: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 group default qlen 1
    inet 10.0.240.4/24 brd 10.0.240.255 scope global dynamic
       valid_lft forever preferred_lft forever
3: eth1: <> mtu 1500 group default qlen 1
    inet 169.254.40.162/16 brd 169.254.255.255 scope global dynamic
       valid_lft forever preferred_lft forever
26: eth2: <> mtu 1500 group default qlen 1
    inet 10.0.11.1/24 brd 10.0.11.255 scope global dynamic
       valid_lft forever preferred_lft forever
1: lo: <LOOPBACK,UP> mtu 1500 group default qlen 1
    inet 127.0.0.1/8 brd 127.255.255.255 scope global dynamic
       valid_lft forever preferred_lft forever
(python) xobs@Cuboid:~$

In this list, eth1 is not a real network device, and attempts to bind to it result in errors. For example, using the aiortc example server, upon clicking "Start" the following error is printed:

Binding to: ('10.0.240.4', 0)
DEBUG:ice:Connection(0) protocol(0) connection_made(<_SelectorDatagramTransport fd=10 read=idle write=<idle, bufsize=0>>)
Binding to: ('169.254.40.162', 0)
ERROR:aiohttp.server:Error handling request
Traceback (most recent call last):
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aiohttp/web_protocol.py", line 416, in start
    resp = yield from self._request_handler(request)
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aiohttp/web.py", line 325, in _handle
    resp = yield from handler(request)
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aiohttp/web_middlewares.py", line 93, in impl
    return (yield from handler(request))
  File "./server.py", line 167, in offer
    await pc.setLocalDescription(answer)
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aiortc/rtcpeerconnection.py", line 317, in setLocalDescription
    await self.__gather()
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aiortc/rtcpeerconnection.py", line 453, in __gather
    await asyncio.gather(*coros)
  File "/usr/lib/python3.5/asyncio/futures.py", line 361, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/usr/lib/python3.5/asyncio/tasks.py", line 296, in _wakeup
    future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aiortc/rtcicetransport.py", line 150, in gather
    await self._connection.gather_candidates()
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aioice/ice.py", line 323, in gather_candidates
    addresses=addresses)
  File "/mnt/d/Code/alzim/python/lib/python3.5/site-packages/aioice/ice.py", line 672, in get_component_candidates
    local_addr=(address, 0))
  File "/usr/lib/python3.5/asyncio/base_events.py", line 844, in create_datagram_endpoint
    raise exceptions[0]
  File "/usr/lib/python3.5/asyncio/base_events.py", line 829, in create_datagram_endpoint
    sock.bind(local_address)
OSError: [Errno 99] Cannot assign requested address

I have modified asyncio to print its address just prior to calling sock.bind(), so you can see the address that's causing the issue.

If I simply omit eth1, then the example works. However, as-is, the library doesn't work under WSL.

Finer local candidate address selection

Currently all addresses on all interfaces are automatically selected as candidates by gather_candidates and the loopback device is only discarded based on the addresses bound to it. While this does work, it may not be desirable for all users, who for example have redundant links where one link is unfit for bulk transfers of data negotiated via ICE (e.g. a volume-limited mobile link). Other addresses should perhaps be ignored from the start because they have no routes to the public internet (e.g. a VPN link to some intranet). This information is not available at the level of aioice but may be available to an application (e.g. via configuration or tight integration with the target system).

Some kind of interface with finer granularity for selecting candidates would enable the use of such information. Probably, one could simply manually modify or set Connection.local_candidates to achieve this, but this feels wrong, since I would consider the member variable private by default. If this is the preferred interface, this should be documented (and the issue closed).

I would argue for additional methods for setting the local candidates:

  • by explicitly adding IPs,
  • by specifying interfaces whose addresses to add,
  • ...

I would implement the changes, but wanted to discuss the preferred API first.

Fallback to STUN Server if TURN connection failes

Hi,

we have a setup where sometimes a STUN server needs to be used (in a company network) and sometimes a TURN server needs to be used (e.g. mobile Hotspot), so we define a STUN server and a TURN server at the same time.

The problem with this setup is, that if we are in the network which blocks the TURN connection, the whole ICE connection failes, because the TURN creat_turn_endpoint raises an error:

Error handling request
Traceback (most recent call last):
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 418, in start
    resp = await task
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aiohttp/web_app.py", line 458, in _handle
    resp = await handler(request)
  File "/home/tanja/git/cidaas/id-card-utility-backend/server.py", line 104, in offer
    await pc.setLocalDescription(answer)
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aiortc/rtcpeerconnection.py", line 666, in setLocalDescription
    await self.__gather()
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aiortc/rtcpeerconnection.py", line 865, in __gather
    await asyncio.gather(*coros)
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aiortc/rtcicetransport.py", line 174, in gather
    await self._connection.gather_candidates()
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/ice.py", line 362, in gather_candidates
    addresses=addresses)
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/ice.py", line 749, in get_component_candidates
    transport=self.turn_transport)
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/turn.py", line 301, in create_turn_endpoint
    await transport._connect()
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/turn.py", line 272, in _connect
    self.__relayed_address = await self.__inner_protocol.connect()
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/turn.py", line 80, in connect
    response, _ = await self.request(request)
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/turn.py", line 173, in request
    return await transaction.run()
  File "/home/tanja/venvs/cidaas/id-services-aggregator/lib/python3.7/site-packages/aioice/stun.py", line 250, in run
    return await self.__future
aioice.exceptions.TransactionTimeout: STUN transaction timed out

A quick fixe for overcoming this issue was to catch the error and to continue with the other servers:
In turn.py create_turn_endpoint I added:

    try:
        await transport._connect()
    except exceptions.TransactionTimeout as e:
        logging.error(e)
        return None, None
    return transport, protocol

And if the protocol is None, the canditate is just not added:
In ice.py get_component_candidates

 _, protocol = await turn.create_turn_endpoint(
                lambda: StunProtocol(self),
                server_addr=self.turn_server,
                username=self.turn_username,
                password=self.turn_password,
                ssl=self.turn_ssl,
                transport=self.turn_transport,
            )
            if not protocol is None:
                protocol = cast(StunProtocol, protocol)

                self._protocols.append(protocol)

                # add relayed candidate
                candidate_address = protocol.transport.get_extra_info("sockname")
                related_address = protocol.transport.get_extra_info("related_address")
                protocol.local_candidate = Candidate(
                    foundation=candidate_foundation("relay", "udp", candidate_address[0]),
                    component=component,
                    transport="udp",
                    priority=candidate_priority(component, "relay"),
                    host=candidate_address[0],
                    port=candidate_address[1],
                    type="relay",
                    related_address=related_address[0],
                    related_port=related_address[1],
                )
                candidates.append(protocol.local_candidate)

But in my opinion that is a dirty fix, as we first need to run into an timeout before beeing able to continue. Do you have any ideas on how to implement this in a proper way? I would also be happy to help with that.

Meaning of `STUN transaction failed (400 - You cannot use the same channel number with different peer)`

Hi!

I'm occasionally getting a bunch of error messages in my aiortc based application that I don't understand.

[webrtc_bridge-1] INFO:aioice.turn:TURN channel bound 16385 ('37.3.111.3', 33340)
[webrtc_bridge-1] ERROR:asyncio:Task exception was never retrieved
[webrtc_bridge-1] future: <Task finished name='Task-1372' coro=<TurnClientMixin.send_data() done, defined at /home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py:241> exception=TransactionFailed(Message(message_method=Method.CHANNEL_BIND, message_class=Class.ERROR, transaction_id=b'\xd1\x9a>\xc5\xfe\xc2\x15e\xd8\x7f\x0b!'))>
[webrtc_bridge-1] Traceback (most recent call last):
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 251, in send_data
[webrtc_bridge-1]     await self.channel_bind(channel, addr)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 85, in channel_bind
[webrtc_bridge-1]     await self.request_with_retry(request)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 213, in request_with_retry
[webrtc_bridge-1]     response, addr = await self.request(request)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 200, in request
[webrtc_bridge-1]     return await transaction.run()
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/stun.py", line 299, in run
[webrtc_bridge-1]     return await self.__future
[webrtc_bridge-1] aioice.stun.TransactionFailed: STUN transaction failed (400 - You cannot use the same channel number with different peer)
[webrtc_bridge-1] INFO:aioice.ice:Connection(2) Check CandidatePair(('172.19.0.2', 35871) -> ('<REPLACED_IP_FOR_CUSTOM_TURN_SERVER>, 58892)) State.IN_PROGRESS -> State.SUCCEEDED
[webrtc_bridge-1] INFO:aioice.ice:Connection(2) ICE completed
[webrtc_bridge-1] ERROR:asyncio:Task exception was never retrieved
[webrtc_bridge-1] future: <Task finished name='Task-1376' coro=<TurnClientMixin.send_data() done, defined at /home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py:241> exception=TransactionFailed(Message(message_method=Method.CHANNEL_BIND, message_class=Class.ERROR, transaction_id=b'2 H\xc6\xb60\rm\x10\x13\x06\x83'))>
[webrtc_bridge-1] Traceback (most recent call last):
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 251, in send_data
[webrtc_bridge-1]     await self.channel_bind(channel, addr)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 85, in channel_bind
[webrtc_bridge-1]     await self.request_with_retry(request)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 213, in request_with_retry
[webrtc_bridge-1]     response, addr = await self.request(request)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 200, in request
[webrtc_bridge-1]     return await transaction.run()
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/stun.py", line 299, in run
[webrtc_bridge-1]     return await self.__future
[webrtc_bridge-1] aioice.stun.TransactionFailed: STUN transaction failed (400 - You cannot use the same channel number with different peer)
[webrtc_bridge-1] INFO:aioice.ice:Connection(0) Consent to send expired
[webrtc_bridge-1] INFO:aioice.turn:TURN allocation deleted ('<REPLACED_IP_FOR_CUSTOM_TURN_SERVER>', 59795)
[webrtc_bridge-1] INFO:aioice.turn:TURN allocation deleted ('<REPLACED_IP_FOR_CUSTOM_TURN_SERVER>', 59795)
[webrtc_bridge-1] ERROR:asyncio:Task exception was never retrieved
[webrtc_bridge-1] future: <Task finished name='Task-2961' coro=<TurnClientMixin.delete() done, defined at /home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py:144> exception=InvalidStateError('invalid state')>
[webrtc_bridge-1] Traceback (most recent call last):
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/turn.py", line 164, in delete
[webrtc_bridge-1]     self.receiver.connection_lost(None)
[webrtc_bridge-1]   File "/home/dyno/.local/lib/python3.8/site-packages/aioice/ice.py", line 174, in connection_lost
[webrtc_bridge-1]     self.__c[webrtc_bridge-1]

I'm mostly concerned about the log: STUN transaction failed (400 - You cannot use the same channel number with different peer).
Both sending video and data over WebRTC seem to work for the most part. Video and data transmission does freeze for a couple of seconds from time to time before it starts working again though. Don't know if that is related.

Is this something I should worry about, or can I safely ignore it?

Channel Binding

Hi
According to https://tools.ietf.org/html/rfc5766#page-14

Channel bindings last for 10 minutes unless refreshed -- this
lifetime was chosen to be longer than the permission lifetime.
Channel bindings are refreshed by sending another ChannelBind request
rebinding the channel to the peer.

I can't find the code about refreshing channel binding.

I am using coturn as turnserver. After 10 minutes coturn will output the log

peer 172.17.0.1 deleted

and the peer will not receive data again.

Add a way of limiting the range of the ephemeral ports that are bound to by gather_candidates

Hi!

Inside get_component_candidates under the Connection class, the local UDP ports are randomly selected by the kernel, since 0 is provided as the port to create_datagram_endpoint. I have a use-case where I need this port range to be limited. Indeed, it's possible to limit it on the whole machine, by setting the /proc/sys/net/ipv4/ip_local_port_range under Linux. However, I need this to be controllable at a more fine grained level.

Could an addition of some kind of allowed_port_range flag be considered? In the implementation, this would require manually trying to bind to ports in the range until success.

Thanks!

RFC 8445

According to the readme, aioice is based on RFC 5245. There is now RFC 8445 that obsoletes RFC 5245: https://datatracker.ietf.org/doc/html/rfc8445#section-21

I am not very familiar with the codebase of aioice or ICE in general, but I didn't see a mention of the new RFC anywhere and thought that it's something that needs to be tracked.

check_periodic should waiting while as less one pair in progress

while test i saw when check_periodic run check_start while prevision paring still in progress
this can produce incorrect nomination

fix like this

    def check_periodic(self) -> bool:
 +       for pair in self._check_list:
 +          if pair.state == CandidatePair.State.IN_PROGRESS:
 +              return True

        # find the highest-priority pair that is in the waiting state

Issue with STUN for consent refresh (RFC7675)

In my setup I have a WebRTC data channel that I use to transfer relatively big data packages of ~30 MB. I observed connection closings when running the code on my embedded system (Jetson Nano). I investigated the errors and noticed that this line here is hit:

self.__log_info("Consent to send expired")

Root cause of that were 6 TransactionTimeouts: https://github.com/aiortc/aioice/blob/main/src/aioice/stun.py#L306 that were accumulated during the entire transfer.

I actually don't know why those timeouts happen (it is reproducible). But upon further digging into the code I notice that no retransmissions are done upon a failure: https://github.com/aiortc/aioice/blob/main/src/aioice/ice.py#L965

I would suggest to increase the retransmissions to at least 1. Or is there a particular reason for setting it to 0?

If anybody would have ideas for the root cause of my TransactionTimeouts it would be very welcome. Could a high system load (sending the data over the datachannel) lead to timeouts because the system misses to answer the Consent checks in time?

If it is of any help: I have wireshark traces of the STUN packages.

STUN with an FQDN fails with uvloop

uvloop's sendto does not perform DNS lookups (which is sensible performance-wise), so if the STUN server is given as an FQDN and not an IP address, sendto fails with:

ValueError: UDP.sendto(): address ('stun.l.google.com', 19302) requires a DNS lookup

Add support for ICE trickle

Currently aioice:

  • emits all local ICE candidates in one go
  • expects remote ICE candidates to be provided in one go

Adding ICE trickle support is highly desirable, but:

  • it adds significant complexity to the state machine
  • it requires rethinking the API through which candidates are provided / retrieved

tests.test_stun.MessageTest fails with python 3.11

Debian-packaged aioice worked fine with python 3.10 but began to fail with python 3.11:

=================================== FAILURES ===================================
_______________________ MessageTest.test_binding_request _______________________

self = <tests.test_stun.MessageTest testMethod=test_binding_request>

    def test_binding_request(self):
        data = read_message("binding_request.bin")

        message = stun.parse_message(data)
        self.assertEqual(message.message_method, stun.Method.BINDING)
        self.assertEqual(message.message_class, stun.Class.REQUEST)
        self.assertEqual(message.transaction_id, b"Nvfx3lU7FUBF")
        self.assertEqual(message.attributes, OrderedDict())

        self.assertEqual(bytes(message), data)
>       self.assertEqual(
            repr(message),
            "Message(message_method=Method.BINDING, message_class=Class.REQUEST, "
            "transaction_id=b'Nvfx3lU7FUBF')",
        )
E       AssertionError: "Mess[14 chars]thod=1, message_class=0, transaction_id=b'Nvfx3lU7FUBF')" != "Mess[14 chars]thod=Method.BINDING, message_class=Class.REQUE[31 chars]BF')"
E       - Message(message_method=1, message_class=0, transaction_id=b'Nvfx3lU7FUBF')
E       ?                        ^                ^
E       + Message(message_method=Method.BINDING, message_class=Class.REQUEST, transaction_id=b'Nvfx3lU7FUBF')
E       ?                        ^^^^^^^^^^^^^^                ^^^^^^^^^^^^^

tests/test_stun.py:101: AssertionError

Support TCP Allocations

RFC 6062 describes a mechanism to access create a TCP TURN allocation. Are there any plans for implementing this mechanism in aioice?

Does aioice support ice-lite?

RFC 5245 is supported according to README, I thought ICE lite implementation was supported as well.

I write a demo running behind a NAT, trying to make a connection with WebRTC gateway which employs ICE lite implemetation.

The demo won't take a role as "controlling agent" while gateway as initiator (OFFERing sdp). This behavior seems not compatible with RFC 5245.

RFC 5245, 2.7. Lite Implementations

When a lite implementation
connects with a full implementation, the full agent takes the role of
the controlling agent, and the lite agent takes on the controlled
role.

Add support for mDNS candidates

Recent browser versions include candidates which do not specify an IP address but instead an mDNS host name. As a first step we should support receiving and using such candidates.

The problem is that using such candidates involves performing a DNS lookup, which is an asynchronous operation. This probably means our current API for providing remote candidates will need to be changed in a backwards-incompatible way:

  • remove the setter for remote_candidates
  • make add_remote_candidate a coroutine

components more then 1 produce incorrect stun srflx

subj

the issue at "get_component_candidates"
1)
there is need add local variable
protocols = []
2)
replace at the body func "get_component_candidates" by protocols.append instead self._protocols.append
3)
and append it at end of "get_component_candidates"
self._protocols += protocols

hope you understand fix and there is no need push request

aioice using sip

Good day! README says that the library allows you to use SIP, but I could not find any references in the source code. Could you explain to me whether it is possible to use SIP?

Failure to parse ip address in offer returned from Chrome

In doing local testing for setting up a WebRTC channel with a simple JS example on the same computer (mac chrome simple JS offer/answer video + aiortc responding with an image), a python exception is happening on one of our dev computers (because of the browser offer returning what Python thinks is a non-parsable ipv6 address) but not on all of them.

The ice candidates that Chrome returned are:

a=candidate:706387448 1 udp 2113937151 5177eebd-d4eb-495a-a75e-0defface8929.local 52832 typ host generation 0 network-cost 999
a=candidate:842163049 1 udp 1677729535 73.170.4.228 52832 typ srflx raddr 0.0.0.0 rport 0 generation 0 network-cost 999

And python3 is returning that it can't parse the ipv6 version of it.

Task exception was never retrieved
future: <Task finished coro=<RTCPeerConnection.__connect() done, defined at /usr/local/lib/python3.6/dist-packages/aiortc/rtcpeerconnection.py:776> exception=ValueError("'5177eebd-d4eb-495a-a75e-0defface8929.local' does not appear to be an IPv4 or IPv6 address",)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/aiortc/rtcpeerconnection.py", line 781, in __connect
    await iceTransport.start(self.__remoteIce[transceiver])
  File "/usr/local/lib/python3.6/dist-packages/aiortc/rtcicetransport.py", line 289, in start
    await self._connection.connect()
  File "/usr/local/lib/python3.6/dist-packages/aioice/ice.py", line 361, in connect
    if (protocol.local_candidate.can_pair_with(remote_candidate) and
  File "/usr/local/lib/python3.6/dist-packages/aioice/candidate.py", line 114, in can_pair_with
    b = ipaddress.ip_address(other.host)
  File "/usr/lib/python3.6/ipaddress.py", line 54, in ip_address
    address)
ValueError: '5177eebd-d4eb-495a-a75e-0defface8929.local' does not appear to be an IPv4 or IPv6 address

On our other computer, these are the candidates which are returned and it does not have this error:

a=candidate:2213748025 1 udp 2122260223 169.254.66.7 61769 typ host generation 0 network-id 3
a=candidate:1940115785 1 udp 2122197247 2600:1700:87d0:3800:a4b1:c744:37d:ebc7 61770 typ host generation 0 network-id 2 network-cost 10
a=candidate:1995739850 1 udp 2122129151 192.168.1.65 65067 typ host generation 0 network-id 1 network-cost 10
a=candidate:2162486046 1 udp 1685921535 162.207.207.120 65067 typ srflx raddr 192.168.1.65 rport 65067 generation 0 network-id 1 network-cost 10
a=candidate:3446727113 1 tcp 1518280447 169.254.66.7 9 typ host tcptype active generation 0 network-id 3
a=candidate:1025519033 1 tcp 1518217471 2600:1700:87d0:3800:a4b1:c744:37d:ebc7 9 typ host tcptype active generation 0 network-id 2 network-cost 10
a=candidate:947351098 1 tcp 1518149375 192.168.1.65 9 typ host tcptype active generation 0 network-id 1 network-cost 10

It seems that the ipv6 "*.local" is the issue here for python parsing the return.

Stale Nonce

Hi
According to https://tools.ietf.org/html/rfc5389#page-27.

If the response is an error response with an error code of 438 (Stale
Nonce), the client MUST retry the request, using the new NONCE
supplied in the 438 (Stale Nonce) response.

I can't find implementation about this in code.

I am using coturn as turnserver. The default nonce lifetime is 600 secs. It will raise a Stale Nonce TransactionFailed after 600 secs.

Get nominated candidate pair and use in another application

I would need to establish a UDP connection between two clients in an application, but the aioice API seems to only support sending data between the nominated pair through send[to] and recv[from].

I was able to get the active pair by calling <Connection>._nominated.get(1) after reading the source, but _nominated is private so I don't think it's supposed to be used.

Is this something that's even supposed to be done using ICE or should I be using STUN directly?

an uncaught TransactionFailed stalls RTCPeerConnection.close()

I have seen the following issue for a while, in which case RTCPeerConnection.close() is blocked forever.

2019-04-10 16:44:51,609 [548366143488] client - event = connection_lost(None)
2019-04-10 16:44:51,610 [548366143488] client - state = CLOSED
2019-04-10 16:44:51,610 [548366143488] client x code = 1000, reason = [no reason]
2019-04-10 16:44:51,612 [548366143488] client x closing TCP connection
2019-04-10 16:44:51,613 [548366143488] finished signaling
2019-04-10 16:44:51,613 [548366143488] cleanup in progress...
2019-04-10 16:44:51,614 [548366143488] closing the peer connection...
2019-04-10 16:44:51,615 [548366143488] sender(video) > RtcpByePacket(sources=[648406895])
2019-04-10 16:44:51,616 [548366143488] sender(video) - RTCP finished
2019-04-10 16:44:51,618 [548366143488] sender(video) > RtcpByePacket(sources=[2211053875])
2019-04-10 16:44:51,618 [548366143488] sender(video) - RTCP finished
2019-04-10 16:44:51,619 [548366143488] sender(video) > RtcpByePacket(sources=[1008964769])
2019-04-10 16:44:51,620 [548366143488] sender(video) - RTCP finished
2019-04-10 16:44:51,621 [548366143488] sender(video) > RtcpByePacket(sources=[1747941862])
2019-04-10 16:44:51,621 [548366143488] sender(video) - RTCP finished
2019-04-10 16:44:51,622 [548366143488] server > AbortChunk(flags=0)
2019-04-10 16:44:51,623 [548366143488] server - State.ESTABLISHED -> State.CLOSED
2019-04-10 16:44:51,623 [548366143488] 1 - open -> closed
2019-04-10 16:44:51,624 [548366143488] controlled - completed -> closed
2019-04-10 16:44:51,624 [548366143488] iceConnectionState changed to closed
2019-04-10 16:44:51,625 [548366143488] Connection(10) protocol(14) connection_lost(None)
2019-04-10 16:44:51,626 [548366143488] Connection(10) protocol(15) connection_lost(None)
2019-04-10 16:44:51,627 [548366143488] Connection(10) protocol(16) connection_lost(None)
2019-04-10 16:44:51,628 [548366143488] Connection(10) protocol(17) connection_lost(None)
2019-04-10 16:44:51,629 [548366143488] Connection(10) protocol(18) connection_lost(None)
2019-04-10 16:44:51,629 [548366143488] Connection(10) protocol(19) connection_lost(None)
2019-04-10 16:44:51,631 [548366143488] turn/udp > ('tk-turn1.xirsys.com', 80) Message(message_method=Method.REFRESH, message_class=Class.REQUEST, transaction_id=b'\xd0\xd5\xd5wB\xbd\xc5\xc6\x146\xd2e')
2019-04-10 16:44:51,666 [548366143488] turn/udp < ('54.199.237.77', 80) Message(message_method=Method.REFRESH, message_class=Class.ERROR, transaction_id=b'\xd0\xd5\xd5wB\xbd\xc5\xc6\x146\xd2e')
2019-04-10 16:44:51,667 [548366143488] Task exception was never retrieved
future: <Task finished coro=<TurnClientMixin.delete() done, defined at /home/nvidia/Downloads/aioice/aioice/turn.py:130> exception=TransactionFailed(Message(message_method=Method.REFRESH, message_class=Class.ERROR, transaction_id=b'\xd0\xd5\xd5wB\xbd\xc5\xc6\x146\xd2e'))>
Traceback (most recent call last):
  File "/home/nvidia/Downloads/aioice/aioice/turn.py", line 141, in delete
    await self.request(request)
  File "/home/nvidia/Downloads/aioice/aioice/turn.py", line 173, in request
    return await transaction.run()
  File "/home/nvidia/Downloads/aioice/aioice/stun.py", line 250, in run
    return await self.__future
aioice.exceptions.TransactionFailed: STUN transaction failed (438 - Wrong nonce^@)

The root cause is, as explicitly the error message says, the loop thread dies because of the uncaught exception.

With the simple fix as below, I have confirmed this no longer happens.

        try:
            await self.request(request)
        except exceptions.TransactionFailed:
            logger.debug('the turn allocation deletion request failed')

The output when the TransactionFailed was raised.

2019-04-10 17:09:59,577 [547505090560] client - event = connection_lost(None)
2019-04-10 17:09:59,578 [547505090560] client - state = CLOSED
2019-04-10 17:09:59,579 [547505090560] client x code = 1000, reason = [no reason]
2019-04-10 17:09:59,580 [547505090560] client x closing TCP connection
2019-04-10 17:09:59,581 [547505090560] finished signaling
2019-04-10 17:09:59,582 [547505090560] cleanup in progress...
2019-04-10 17:09:59,583 [547505090560] closing the peer connection...
2019-04-10 17:09:59,584 [547505090560] sender(video) > RtcpByePacket(sources=[2531309811])
2019-04-10 17:09:59,585 [547505090560] sender(video) - RTCP finished
2019-04-10 17:09:59,587 [547505090560] sender(video) > RtcpByePacket(sources=[1615847512])
2019-04-10 17:09:59,588 [547505090560] sender(video) - RTCP finished
2019-04-10 17:09:59,590 [547505090560] sender(video) > RtcpByePacket(sources=[1579158080])
2019-04-10 17:09:59,591 [547505090560] sender(video) - RTCP finished
2019-04-10 17:09:59,593 [547505090560] sender(video) > RtcpByePacket(sources=[987792055])
2019-04-10 17:09:59,593 [547505090560] sender(video) - RTCP finished
2019-04-10 17:09:59,594 [547505090560] server > AbortChunk(flags=0)
2019-04-10 17:09:59,596 [547505090560] server - State.ESTABLISHED -> State.CLOSED
2019-04-10 17:09:59,596 [547505090560] 1 - open -> closed
2019-04-10 17:09:59,597 [547505090560] controlled - completed -> closed
2019-04-10 17:09:59,597 [547505090560] iceConnectionState changed to closed
2019-04-10 17:09:59,599 [547505090560] Connection(0) protocol(0) connection_lost(None)
2019-04-10 17:09:59,600 [547505090560] Connection(0) protocol(1) connection_lost(None)
2019-04-10 17:09:59,600 [547505090560] Connection(0) protocol(2) connection_lost(None)
2019-04-10 17:09:59,601 [547505090560] Connection(0) protocol(3) connection_lost(None)
2019-04-10 17:09:59,602 [547505090560] Connection(0) protocol(4) connection_lost(None)
2019-04-10 17:09:59,603 [547505090560] Connection(0) protocol(5) connection_lost(None)
2019-04-10 17:09:59,605 [547505090560] turn/udp > ('tk-turn2.xirsys.com', 80) Message(message_method=Method.REFRESH, message_class=Class.REQUEST, transaction_id=b'z\xc7\xa3X\x17\x998\xeb\x8a\xd8\xe3\x14')
2019-04-10 17:09:59,622 [547505090560] turn/udp < ('52.194.163.111', 80) Message(message_method=Method.REFRESH, message_class=Class.ERROR, transaction_id=b'z\xc7\xa3X\x17\x998\xeb\x8a\xd8\xe3\x14')
2019-04-10 17:09:59,623 [547505090560] the turn allocation deletion request failed
2019-04-10 17:09:59,624 [547505090560] TURN allocation deleted ('172.26.15.73', 64919)
2019-04-10 17:09:59,624 [547505090560] Connection(0) protocol(6) connection_lost(None)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.