'[WinError 10022] An invalid argument was supplied' when using Python asyncio.loop.create_datagram_endpoint and IPv6

Question:

When I execute the following script I get

Send: Hello World!
Error received: [WinError 10022] An invalid argument was supplied
Error received: [WinError 10022] An invalid argument was supplied
Connection closed
import asyncio
import socket

class EchoClientProtocol:
    def __init__(self, message, on_con_lost):
        self.message = message
        self.on_con_lost = on_con_lost
        self.transport = None

    def connection_made(self, transport):
        self.transport = transport
        print('Send:', self.message)
        self.transport.sendto(self.message.encode(), ("::1", 60000))

    def datagram_received(self, data, addr):
        print("Received:", data.decode())

        print("Close the socket")
        self.transport.close()

    def error_received(self, exc):
        print('Error received:', exc)

    def connection_lost(self, exc):
        print("Connection closed")
        self.on_con_lost.set_result(True)


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    on_con_lost = loop.create_future()
    message = "Hello World!"

    sock = socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM)
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: EchoClientProtocol(message, on_con_lost),
        sock = sock)

    try:
        await on_con_lost
    finally:
        transport.close()

asyncio.run(main())

The example is from here. The only changes I made:

  • Using an already created IPv6 socket by using the sock argument of create_datagram_endpoint.
    • I have to do it this way, because I have to specify some options on the socket which I omitted in the example.
  • Providing an address ("::1", 60000) in the transport.sendto function.

Can anyone explain what I am doing wrong and how to fix this?

I use Python 3.11.2 on Windows 10 21H2.

I have tried IPv4 (changing AF_INET6 to AF_INET and ::1 to 127.0.0.1 as well). Then the example works just fine.
Using socket without the async framework also works, even for IPv6:

sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sock.sendto(b"11", ("::1", 60000))
Asked By: user18342360

||

Answers:

The solution is to provide the flow info and scope id in the sendto function:

self.transport.sendto(self.message.encode(), ("::1", 60000, 0, 0))

In addition there might be a second problem:
When the sendto function is not called in the connection_made callback, then the asyncio framework calls ov.WSARecvFrom next which results in the same error ([WinError 10022] An invalid argument was supplied) because the socket is in the wrong state (receiving but no address is provided). The solution is to bind the socket before calling create_datagram_endpoint: sock.bind(("", 0))

Some related bugs for further reading:

Answered By: user18342360