MagicStack / MagicStack/uvloop

Missing asyncio feature regarding support of SCTP sockets

Ouverte
#467 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Langage dominant
Cython
Étoiles
11.9k
Forks
616
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

  • uvloop version: v0.16.0

  • Python version: 3.10.4

  • Platform: Linux
    Please note that SCTP works only if you have libsctp(-dev) installed on your Linux system. MacOS doesn't support SCTP without further effort and Windows I cannot say, since I don't use WIndows.

  • Can you reproduce the bug with PYTHONASYNCIODEBUG in env?: Please clarify if you expect further output from uvloop in this case. The error is an OS error as a result of uvloop OS socket configuration, so there is no additional output I can see other than the output below. I will edit my post accordingly if I am missing information or should try something else. Thanks!

  • Does uvloop behave differently from vanilla asyncio? How?: Yes, in vanilla asyncio, SCTP sockets can be used out of the box, behaving in the same way TCP stream sockets do.

I am using SCTP as the transport stream protocol, which works fine with Python vanilla asyncio. Asyncio shows the same behaviour as for TCP sockets, which is expected since both transport protocols are stream protocols. However, uvloop seems to only allow TCP sockets.

Server side SCTP socket issue observation
When using an SCTP listening server socket, starting the server works fine. As soon as a SCTP client tries to connect, no connection notification or client data ever reaches the Python application code, but also no errors or other messages are observed on application layer. Wireshark shows that the connection from the client is dropped by the server side right after the SCTP cookie ack handshake.

Client side SCTP socket issue observation
Using an SCTP socket for a client raises an OS Error. From the stack trace it looks like uvloop is trying to use TCP under the hood instead of the SCTP socket.

Traceback (most recent call last):
  File "/home/lukas/uvloop-bug-report/client.py", line 69, in <module>
    asyncio.run(start_client(use_sctp=True))
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "uvloop/loop.pyx", line 1501, in uvloop.loop.Loop.run_until_complete
  File "/home/lukas/uvloop-bug-report/client.py", line 55, in start_client
    transport, _ = await asyncio.get_event_loop().create_connection(
  File "uvloop/loop.pyx", line 2057, in create_connection
  File "uvloop/loop.pyx", line 2045, in uvloop.loop.Loop.create_connection
  File "uvloop/handles/tcp.pyx", line 177, in uvloop.loop.TCPTransport._open
  File "uvloop/handles/tcp.pyx", line 34, in uvloop.loop.__tcp_open
OSError: [Errno 92] Protocol not available

To reproduce the issues, the following client/server code can be used on a Linux machine or wrapped in a Linux based Docker container if you don't have a Linux OS (I am happy to help with this if needed).

Server code

"""
The server side of the asyncio/uvloop SCTP socket demo.

Author: Lukas Stermann <lukas@ng-voice.com>
"""
import asyncio
import socket
from typing import Optional
import uvloop


class ServerProtocol(asyncio.Protocol):
    """
    This is just a dummy server echo protocol for demo purposes.
    """
    def __init__(self):
        self.transport: Optional[asyncio.Transport] = None

    def connection_made(self, transport: asyncio.Transport) -> None:
        self.transport = transport
        print('New connection from client')

    def data_received(self, raw_data: bytes) -> None:
        print(f'Data received from client: <{raw_data.decode()}>')
        self.transport.write(b'pong')

    def connection_lost(self, exc: Optional[Exception]) -> None:
        print('Connection lost')
        self.transport.close()


async def start_server(use_sctp: bool = True) -> None:
    """
    This server starts a listening TCP/SCTP socket.

    :param use_sctp: Whether the server shall use SCTP.
                     If set to False it uses TCP.
    """
    socket_: socket.socket

    if use_sctp:
        socket_ = socket.socket(socket.AF_INET6,
                                socket.SOCK_STREAM,
                                socket.IPPROTO_SCTP)
    else:
        socket_ = socket.socket(socket.AF_INET6,
                                socket.SOCK_STREAM)

    socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    if socket.has_dualstack_ipv6():
        socket_.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
    else:
        print('This OS does not support IPv4/IPv6 dual stack. '
              'Falling back to IPv6 only.')

    socket_.setblocking(False)
    socket_.bind(('', 10002))
    socket_.listen()

    connection_settings: dict = {
        'protocol_factory': lambda: ServerProtocol(),
        'sock': socket_}

    server = await asyncio.get_event_loop().create_server(**connection_settings)
    await server.serve_forever()


# Comment in the following line to see the issue with using an SCTP socket in uvloop.
# None of the print statements in the protocol are executed and the socket is closed
# right after the SCTP cookie handshake (use Wireshark to see this behaviour or see attached pcap).
uvloop.install()

# Switch between TCP and SCTP to see that for uvloop TCP works well, while SCTP doesn't
asyncio.run(start_server(use_sctp=True))

Client code

"""
The client side of the asyncio/uvloop SCTP socket demo.

Author: Lukas Stermann <lukas@ng-voice.com>
"""
import asyncio
import socket
from typing import Optional
import uvloop


class ClientProtocol(asyncio.Protocol):
    """
    This is just a dummy client echo protocol for demo purposes.
    """
    def __init__(self):
        self.transport: Optional[asyncio.Transport] = None

    def connection_made(self, transport: asyncio.Transport) -> None:
        self.transport = transport
        print('Connection established to server')
        self.transport.write(b'ping')

    def data_received(self, raw_data: bytes) -> None:
        print(f'Answer received from server: <{raw_data.decode()}>')

    def connection_lost(self, exc: Optional[Exception]) -> None:
        print('Connection lost')
        self.transport.close()


async def start_client(use_sctp: bool = True) -> None:
    """
    This client sends a message over TCP/SCTP socket.

    :param use_sctp: Whether the server shall use SCTP.
                     If set to False it uses TCP.
    """
    connection_parameters: dict = {
        'protocol_factory': lambda: ClientProtocol()
    }

    if use_sctp:
        socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM,
                                socket.IPPROTO_SCTP)
    else:
        socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    socket_.setblocking(False)
    connection_parameters['sock'] = socket_

    await asyncio.get_event_loop().sock_connect(
                socket_, ('127.0.0.1', 10002))

    transport, _ = await asyncio.get_event_loop().create_connection(
        **connection_parameters)

    await asyncio.sleep(2)
    transport.close()

# Comment in the following line to see the issue with using an SCTP socket in uvloop.
# The client will crash with OSError saying the protocol is not supported
uvloop.install()

# Switch between TCP and SCTP to see that for uvloop TCP works well, while SCTP doesn't
asyncio.run(start_client(use_sctp=True))

Please also find attached a zipped PCAP for Wireshark. The first part of the trace there is the vanilla asyncio version with the uvloop.install() command commented out in both client and server code. The second trace shows the behavior when the server is using uvloop but the client is using vanilla asyncio.

Would be great if you would comment on this, cheers for your work, really appreciate that you are bringing performance to Python :)

uvloop_sctp_issue.zip

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Exécutez la reproduction SCTP fournie pour le client et le serveur sous Linux avec libsctp installé, en comparant asyncio vanilla à uvloop. Inspectez ensuite uvloop/handles/tcp.pyx et les chemins de create_connection dans uvloop/loop.pyx. C’est terminé lorsque les connexions SCTP du client et du serveur fonctionnent avec uvloop sans l’erreur de protocole signalée ni déconnexion.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
networking
Type d'issue
Fonctionnalité
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.