From d9589a5310f61a1558af5de8458ef16831b19043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Tue, 18 Oct 2022 01:35:29 +0200 Subject: [PATCH 01/10] [tests] update github actions to latest versions --- .github/workflows/tests.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0e6892..d5cf911 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,8 +7,8 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: 3.7 - name: Install packages @@ -31,8 +31,8 @@ jobs: - '3.8' - '3.7' steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Disable firewall @@ -48,14 +48,14 @@ jobs: coverage run -m unittest discover -v coverage xml - name: Upload coverage report - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 package: runs-on: ubuntu-latest needs: [lint, test] steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: 3.7 - name: Install packages From 2aed57f1fa0ffeedc3a5f257a8fbbace07ca2c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Tue, 18 Oct 2022 08:13:24 +0200 Subject: [PATCH 02/10] Fix type hints for asyncio protocols and transports. --- src/aioice/ice.py | 2 -- src/aioice/mdns.py | 2 +- src/aioice/turn.py | 21 ++++++++++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/aioice/ice.py b/src/aioice/ice.py index f33a8dc..0eb99bd 100644 --- a/src/aioice/ice.py +++ b/src/aioice/ice.py @@ -865,7 +865,6 @@ async def get_component_candidates( except OSError as exc: self.__log_info("Could not bind to %s - %s", address, exc) continue - protocol = cast(StunProtocol, protocol) host_protocols.append(protocol) # add host candidate @@ -911,7 +910,6 @@ async def get_component_candidates( ssl=self.turn_ssl, transport=self.turn_transport, ) - protocol = cast(StunProtocol, protocol) self._protocols.append(protocol) # add relayed candidate diff --git a/src/aioice/mdns.py b/src/aioice/mdns.py index 2eac59e..7982a9c 100644 --- a/src/aioice/mdns.py +++ b/src/aioice/mdns.py @@ -189,4 +189,4 @@ async def create_mdns_protocol() -> MDnsProtocol: sock=rx_sock, ) - return cast(MDnsProtocol, protocol) + return protocol diff --git a/src/aioice/turn.py b/src/aioice/turn.py index 360cf50..c54c9e6 100644 --- a/src/aioice/turn.py +++ b/src/aioice/turn.py @@ -4,7 +4,18 @@ import socket import struct import time -from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union, cast +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Text, + Tuple, + TypeVar, + Union, + cast, +) from . import stun from .utils import random_transaction_id @@ -17,6 +28,8 @@ UDP_TRANSPORT = 0x11000000 UDP_SOCKET_BUFFER_SIZE = 262144 +_ProtocolT = TypeVar("_ProtocolT", bound=asyncio.BaseProtocol) + def is_channel_data(data: bytes) -> bool: return (data[0] & 0xC0) == 0x40 @@ -371,7 +384,7 @@ async def _connect(self) -> None: async def create_turn_endpoint( - protocol_factory: Callable, + protocol_factory: Callable[[], _ProtocolT], server_addr: Tuple[str, int], username: Optional[str], password: Optional[str], @@ -379,11 +392,13 @@ async def create_turn_endpoint( channel_refresh_time: int = DEFAULT_CHANNEL_REFRESH_TIME, ssl: bool = False, transport: str = "udp", -) -> Tuple[TurnTransport, asyncio.Protocol]: +) -> Tuple[TurnTransport, _ProtocolT]: """ Create datagram connection relayed over TURN. """ loop = asyncio.get_event_loop() + inner_protocol: asyncio.BaseProtocol + inner_transport: asyncio.BaseTransport if transport == "tcp": inner_transport, inner_protocol = await loop.create_connection( lambda: TurnClientTcpProtocol( From bc3d72cfc6923201a98beb2249a5fe9aff9f2d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Wed, 26 Oct 2022 16:54:39 +0200 Subject: [PATCH 03/10] =?UTF-8?q?[package]=C2=A0support=20Python=203.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 1 + setup.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5cf911..b04043b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,6 +26,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python: + - '3.11' - '3.10' - '3.9' - '3.8' diff --git a/setup.py b/setup.py index f82bc47..9154239 100644 --- a/setup.py +++ b/setup.py @@ -35,9 +35,11 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], package_dir={"": "src"}, package_data={"aioice": ["py.typed"]}, packages=["aioice"], install_requires=["dnspython>=2.0.0", "netifaces"], + python_requires=">=3.7", ) From 9a63c1117a6708f8cecca43d874330b51d34937b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Mon, 30 Jan 2023 11:57:03 +0100 Subject: [PATCH 04/10] [message] Fix string representation for Python 3.11 We also move casting message method and class to the `parse_message` method to clarify the error path. --- src/aioice/stun.py | 22 ++++++++++++++-------- tests/test_stun.py | 5 +++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/aioice/stun.py b/src/aioice/stun.py index bb8740f..7aefb5b 100644 --- a/src/aioice/stun.py +++ b/src/aioice/stun.py @@ -198,8 +198,8 @@ def __init__( transaction_id: Optional[bytes] = None, attributes: Optional[OrderedDict] = None, ) -> None: - self.message_method = Method(message_method) - self.message_class = Class(message_class) + self.message_method = message_method + self.message_class = message_class self.transaction_id = transaction_id or random_transaction_id() self.attributes = attributes or OrderedDict() @@ -238,10 +238,13 @@ def __bytes__(self) -> bytes: ) def __repr__(self) -> str: - return "Message(message_method=%s, message_class=%s, transaction_id=%s)" % ( - self.message_method, - self.message_class, - repr(self.transaction_id), + return ( + "Message(message_method=Method.%s, message_class=Class.%s, transaction_id=%s)" + % ( + self.message_method.name, + self.message_class.name, + repr(self.transaction_id), + ) ) @@ -351,9 +354,12 @@ def parse_message(data: bytes, integrity_key: Optional[bytes] = None) -> Message raise ValueError("STUN message integrity does not match") pos += 4 + attr_len + pad_len + return Message( - message_method=message_type & 0x3EEF, - message_class=message_type & 0x0110, + # An unknown method raises a `ValueError`. + message_method=Method(message_type & 0x3EEF), + # This cast cannot fail, as all 4 possible classes are defined. + message_class=Class(message_type & 0x0110), transaction_id=transaction_id, attributes=attributes, ) diff --git a/tests/test_stun.py b/tests/test_stun.py index 2bdd70d..fd394fa 100644 --- a/tests/test_stun.py +++ b/tests/test_stun.py @@ -201,6 +201,11 @@ def test_message_shorter_than_header(self): stun.parse_message(b"123") self.assertEqual(str(cm.exception), "STUN message length is less than 20 bytes") + def test_message_with_unknown_method(self): + with self.assertRaises(ValueError) as cm: + stun.parse_message(bytes(20)) + self.assertEqual(str(cm.exception), "0 is not a valid Method") + class TransactionTest(unittest.TestCase): def setUp(self): From 1c1d65f260c40338f4609624ec4f5b0af820acbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Wed, 26 Oct 2022 20:22:24 +0200 Subject: [PATCH 05/10] [tests] fix SSLContext deprecation warnings If no `protocol` argument is passed to the `ssl.SSLContext()` constructor, it defaults to `ssl.PROTOCOL_TLS` which is deprecated since Python 3.10. Explicitly use `ssl.PROTOCOL_TLS_CLIENT` or `ssl.PROTOCOL_TLS_SERVER` to fix the depreaction warning as these constants have been available since Python 3.6. --- tests/test_turn.py | 3 ++- tests/turnserver.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_turn.py b/tests/test_turn.py index 5f4d025..b8bccb1 100644 --- a/tests/test_turn.py +++ b/tests/test_turn.py @@ -63,7 +63,8 @@ async def test_tcp_transport(self): @asynctest async def test_tls_transport(self): - ssl_context = ssl.SSLContext() + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE await self._test_transport("tcp", "tls_address", ssl=ssl_context) diff --git a/tests/turnserver.py b/tests/turnserver.py index 6860fd2..f2ca4be 100644 --- a/tests/turnserver.py +++ b/tests/turnserver.py @@ -351,7 +351,7 @@ async def listen(self, port=0, tls_port=0): logger.info("Listening for UDP on %s", self.udp_address) # listen for TLS - ssl_context = ssl.SSLContext() + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(CERT_FILE, KEY_FILE) self.tls_server = await loop.create_server( lambda: TurnServerTcpProtocol(server=self), From 9b2875e889a77ea1aad3485b15cc0a14cec5ba36 Mon Sep 17 00:00:00 2001 From: Erik Moqvist Date: Fri, 27 Jan 2023 23:02:05 +0100 Subject: [PATCH 06/10] TCP requires padding to multiple of 4 bytes (RFC 8656 12.5) Co-authored-by: Jeremy Laine --- src/aioice/stun.py | 17 ++++++++++++++--- src/aioice/turn.py | 10 +++++++++- tests/test_turn.py | 9 ++++++--- tests/turnserver.py | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/aioice/stun.py b/src/aioice/stun.py index 7aefb5b..01c1251 100644 --- a/src/aioice/stun.py +++ b/src/aioice/stun.py @@ -224,8 +224,8 @@ def __bytes__(self) -> bytes: v = attr_pack(attr_value) attr_len = len(v) - pad_len = 4 * ((attr_len + 3) // 4) - attr_len - data += pack("!HH", attr_type, attr_len) + v + (b"\x00" * pad_len) + pad_len = padding_length(attr_len) + data += pack("!HH", attr_type, attr_len) + v + bytes(pad_len) return ( pack( "!HHI12s", @@ -317,6 +317,17 @@ def __retry(self) -> None: self.__tries += 1 +def padding_length(length: int) -> int: + """ + STUN message attributes are padded to a 4-byte boundary. + """ + rest = length % 4 + if rest == 0: + return 0 + else: + return 4 - rest + + def parse_message(data: bytes, integrity_key: Optional[bytes] = None) -> Message: """ Parses a STUN message. @@ -336,7 +347,7 @@ def parse_message(data: bytes, integrity_key: Optional[bytes] = None) -> Message while pos <= len(data) - 4: attr_type, attr_len = unpack("!HH", data[pos : pos + 4]) v = data[pos + 4 : pos + 4 + attr_len] - pad_len = 4 * ((attr_len + 3) // 4) - attr_len + pad_len = padding_length(attr_len) if attr_type in ATTRIBUTES_BY_TYPE: _, attr_name, attr_pack, attr_unpack = ATTRIBUTES_BY_TYPE[attr_type] if attr_unpack == unpack_xor_address: diff --git a/src/aioice/turn.py b/src/aioice/turn.py index c54c9e6..481ab95 100644 --- a/src/aioice/turn.py +++ b/src/aioice/turn.py @@ -50,6 +50,7 @@ def data_received(self, data: bytes) -> None: while len(self.buffer) >= 4: _, length = struct.unpack("!HH", self.buffer[0:4]) + length += stun.padding_length(length) if is_channel_data(self.buffer): full_length = 4 + length else: @@ -61,6 +62,13 @@ def data_received(self, data: bytes) -> None: self.datagram_received(self.buffer[0:full_length], addr) self.buffer = self.buffer[full_length:] + def _padded(self, data: bytes) -> bytes: + # TCP and TCP-over-TLS must pad messages to 4-byte boundaries. + padding = stun.padding_length(len(data)) + if padding: + data += bytes(padding) + return data + class TurnClientMixin: _send: Callable @@ -319,7 +327,7 @@ class TurnClientTcpProtocol(TurnClientMixin, TurnStreamMixin, asyncio.Protocol): """ def _send(self, data: bytes) -> None: - self.transport.write(data) + self.transport.write(self._padded(data)) def __repr__(self) -> str: return "turn/tcp" diff --git a/tests/test_turn.py b/tests/test_turn.py index b8bccb1..95bc550 100644 --- a/tests/test_turn.py +++ b/tests/test_turn.py @@ -140,10 +140,13 @@ async def _test_transport_ok_multi(self, *, transport, server_addr_attr, ssl): self.assertIsNone(transport.get_extra_info("peername")) self.assertIsNotNone(transport.get_extra_info("sockname")) - # bind channel, send ping, expect pong + # Bind channel, send ping, expect pong. + # + # We use different lengths to trigger both padded an unpadded + # ChannelData messages over TCP. async with run_echo_server() as echo_server1: async with run_echo_server() as echo_server2: - transport.sendto(b"ping10", echo_server1.udp_address) + transport.sendto(b"ping", echo_server1.udp_address) # never padded transport.sendto(b"ping11", echo_server1.udp_address) transport.sendto(b"ping20", echo_server2.udp_address) transport.sendto(b"ping21", echo_server2.udp_address) @@ -151,7 +154,7 @@ async def _test_transport_ok_multi(self, *, transport, server_addr_attr, ssl): self.assertEqual( sorted(protocol.received), [ - (b"ping10", echo_server1.udp_address), + (b"ping", echo_server1.udp_address), (b"ping11", echo_server1.udp_address), (b"ping20", echo_server2.udp_address), (b"ping21", echo_server2.udp_address), diff --git a/tests/turnserver.py b/tests/turnserver.py index f2ca4be..5525679 100644 --- a/tests/turnserver.py +++ b/tests/turnserver.py @@ -294,7 +294,7 @@ def send_stun(self, message, addr): class TurnServerTcpProtocol(TurnServerMixin, TurnStreamMixin, asyncio.Protocol): def _send(self, data, addr): - self.transport.write(data) + self.transport.write(self._padded(data)) class TurnServerUdpProtocol(TurnServerMixin, asyncio.DatagramProtocol): From bbe0fd361d5fa55b4b1b525c1225b5a6dd532f0b Mon Sep 17 00:00:00 2001 From: kennytheeggman Date: Thu, 29 Dec 2022 14:54:52 -0500 Subject: [PATCH 07/10] [issues] fix for possible ICE gathering failure check_incoming is skipped when _check_list is not populated early enough aiortc/aiortc#801 --- src/aioice/ice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aioice/ice.py b/src/aioice/ice.py index 0eb99bd..8813f3a 100644 --- a/src/aioice/ice.py +++ b/src/aioice/ice.py @@ -325,6 +325,7 @@ def __init__( self._early_checks: List[ Tuple[stun.Message, Tuple[str, int], StunProtocol] ] = [] + self._early_checks_done = False self._event_waiter: Optional[asyncio.Future[ConnectionEvent]] = None self._id = next(connection_id) self._local_candidates: List[Candidate] = [] @@ -469,6 +470,7 @@ async def connect(self) -> None: for early_check in self._early_checks: self.check_incoming(*early_check) self._early_checks = [] + self._early_checks_done = True # perform checks while True: @@ -1021,7 +1023,7 @@ def request_received( response.add_message_integrity(self.local_password.encode("utf8")) protocol.send_stun(response, addr) - if not self._check_list: + if not self._check_list and not self._early_checks_done: self._early_checks.append((message, addr, protocol)) else: self.check_incoming(message, addr, protocol) From daedc1e61b10ee67529aa6a3149dcdbc40cc9b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Mon, 30 Jan 2023 17:28:00 +0100 Subject: [PATCH 08/10] [tests] exercise condition description in issue #801 --- tests/test_ice.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_ice.py b/tests/test_ice.py index fb5e308..d732d4b 100644 --- a/tests/test_ice.py +++ b/tests/test_ice.py @@ -267,6 +267,51 @@ async def test_connect_early_checks(self): await conn_a.close() await conn_b.close() + @asynctest + async def test_connect_early_checks_2(self): + conn_a = ice.Connection(ice_controlling=True) + conn_b = ice.Connection(ice_controlling=False) + + # both sides gather local candidates and exchange credentials + await conn_a.gather_candidates() + await conn_b.gather_candidates() + conn_a.remote_username = conn_b.local_username + conn_a.remote_password = conn_b.local_password + conn_b.remote_username = conn_a.local_username + conn_b.remote_password = conn_a.local_password + + async def connect_b(): + # side B receives offer and connects + for candidate in conn_a.local_candidates: + await conn_b.add_remote_candidate(candidate) + await conn_b.add_remote_candidate(None) + await conn_b.connect() + + # side A receives candidates + for candidate in conn_b.local_candidates: + await conn_a.add_remote_candidate(candidate) + await conn_a.add_remote_candidate(None) + + # The sequence is: + # - side A starts connecting immediately, but has no candidates + # - side B receives candidates and connects + # - side A receives candidates, and connection completes + await asyncio.gather(conn_a.connect(), connect_b()) + + # send data a -> b + await conn_a.send(b"howdee") + data = await conn_b.recv() + self.assertEqual(data, b"howdee") + + # send data b -> a + await conn_b.send(b"gotcha") + data = await conn_a.recv() + self.assertEqual(data, b"gotcha") + + # close + await conn_a.close() + await conn_b.close() + @asynctest async def test_connect_two_components(self): conn_a = ice.Connection(ice_controlling=True, components=2) From e56c96cf0b4213dba1d43135f47514a54473ceff Mon Sep 17 00:00:00 2001 From: Erik Moqvist Date: Thu, 19 Jan 2023 22:53:57 +0100 Subject: [PATCH 09/10] Optionally force gathering of only relay (STUN/TURN) candidates. --- src/aioice/__init__.py | 2 +- src/aioice/ice.py | 30 +++++++++++++++++++++++++++- tests/test_ice.py | 45 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/aioice/__init__.py b/src/aioice/__init__.py index 1986c77..6eb0599 100644 --- a/src/aioice/__init__.py +++ b/src/aioice/__init__.py @@ -3,7 +3,7 @@ from .about import __version__ from .candidate import Candidate -from .ice import Connection, ConnectionClosed +from .ice import Connection, ConnectionClosed, TransportPolicy # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/src/aioice/ice.py b/src/aioice/ice.py index 8813f3a..e809acc 100644 --- a/src/aioice/ice.py +++ b/src/aioice/ice.py @@ -30,6 +30,20 @@ _mdns = threading.local() +class TransportPolicy(enum.Enum): + ALL = 0 + """ + All ICE candidates will be considered. + """ + + RELAY = 1 + """ + Only ICE candidates whose IP addresses are being relayed, + such as those being passed through a STUN or TURN server, + will be considered. + """ + + async def get_or_create_mdns_protocol(subscriber: object) -> mdns.MDnsProtocol: if not hasattr(_mdns, "lock"): _mdns.lock = asyncio.Lock() @@ -282,6 +296,7 @@ class Connection: :param turn_transport: The transport for TURN server, `"udp"` or `"tcp"`. :param use_ipv4: Whether to use IPv4 candidates. :param use_ipv6: Whether to use IPv6 candidates. + :param transport_policy: Transport policy. """ def __init__( @@ -296,6 +311,7 @@ def __init__( turn_transport: str = "udp", use_ipv4: bool = True, use_ipv6: bool = True, + transport_policy: TransportPolicy = TransportPolicy.ALL, ) -> None: self.ice_controlling = ice_controlling #: Local username, automatically set to a random value. @@ -342,6 +358,17 @@ def __init__( self._use_ipv4 = use_ipv4 self._use_ipv6 = use_ipv6 + if ( + stun_server is None + and turn_server is None + and transport_policy == TransportPolicy.RELAY + ): + raise ValueError( + "Relay transport policy requires a STUN and/or TURN server." + ) + + self._transport_policy = transport_policy + @property def local_candidates(self) -> List[Candidate]: """ @@ -880,7 +907,8 @@ async def get_component_candidates( port=candidate_address[1], type="host", ) - candidates.append(protocol.local_candidate) + if self._transport_policy == TransportPolicy.ALL: + candidates.append(protocol.local_candidate) self._protocols += host_protocols # query STUN server for server-reflexive candidates (IPv4 only) diff --git a/tests/test_ice.py b/tests/test_ice.py index d732d4b..866e6b6 100644 --- a/tests/test_ice.py +++ b/tests/test_ice.py @@ -5,7 +5,7 @@ import unittest from unittest import mock -from aioice import Candidate, ice, mdns, stun +from aioice import Candidate, TransportPolicy, ice, mdns, stun from .turnserver import run_turn_server from .utils import asynctest, invite_accept @@ -1200,6 +1200,49 @@ async def test_gather_candidates_oserror(self, mock_create): await conn.gather_candidates() self.assertEqual(conn.local_candidates, []) + @asynctest + async def test_gather_candidates_relay_only_no_servers(self): + with self.assertRaises(ValueError) as cm: + ice.Connection(ice_controlling=True, transport_policy=TransportPolicy.RELAY) + self.assertEqual( + str(cm.exception), + "Relay transport policy requires a STUN and/or TURN server.", + ) + + @asynctest + async def test_gather_candidates_relay_only_with_stun_server(self): + async with run_turn_server() as stun_server: + conn_a = ice.Connection( + ice_controlling=True, + stun_server=stun_server.udp_address, + transport_policy=TransportPolicy.RELAY, + ) + conn_b = ice.Connection(ice_controlling=False) + + # invite / accept + await invite_accept(conn_a, conn_b) + + # we whould only have a server-reflexive candidate in connection a + self.assertCandidateTypes(conn_a, set(["srflx"])) + + @asynctest + async def test_gather_candidates_relay_only_with_turn_server(self): + async with run_turn_server(users={"foo": "bar"}) as turn_server: + conn_a = ice.Connection( + ice_controlling=True, + turn_server=turn_server.udp_address, + turn_username="foo", + turn_password="bar", + transport_policy=TransportPolicy.RELAY, + ) + conn_b = ice.Connection(ice_controlling=False) + + # invite / accept + await invite_accept(conn_a, conn_b) + + # we whould only have a server-reflexive candidate in connection a + self.assertCandidateTypes(conn_a, set(["relay"])) + @asynctest async def test_repr(self): conn = ice.Connection(ice_controlling=True) From 3cd4bcc9f710d335d3b3ef8ae7a786830712f24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Mon, 30 Jan 2023 18:04:28 +0100 Subject: [PATCH 10/10] 0.8.0 --- src/aioice/about.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aioice/about.py b/src/aioice/about.py index 3d80d4a..118e72a 100644 --- a/src/aioice/about.py +++ b/src/aioice/about.py @@ -4,4 +4,4 @@ __summary__ = "An implementation of Interactive Connectivity Establishment (RFC 5245)" __title__ = "aioice" __uri__ = "https://github.com/aiortc/aioice" -__version__ = "0.7.7" +__version__ = "0.8.0"