> www/py-aioquic has an issue, IPV6_V6ONLY is neutered on OpenBSD so > it is broken, so I'm not entirely happy importing it as-is (although > mitmproxy itself does work as long as you don't use the quic support).
I tried the attached patch as a dumb attempt. With that, I picked a v4-only example from https://bagder.github.io/HTTP3-test/ which works correctly with curl --http3 and tried this: $ cd `make show=WRKSRC` $ python3 examples/http3_client.py -v https://pgjones.dev/ which looks successful: 2024-06-22 12:47:13,379 DEBUG asyncio Using selector: KqueueSelector 2024-06-22 12:47:13,520 DEBUG quic [5af065b80f12c352] TLS State.CLIENT_HANDSHAKE_START -> State.CLIENT_EXPECT_SERVER_HELLO 2024-06-22 12:47:13,548 DEBUG quic [5af065b80f12c352] QuicConnectionState.FIRSTFLIGHT -> QuicConnectionState.CONNECTED 2024-06-22 12:47:13,550 DEBUG quic [5af065b80f12c352] TLS State.CLIENT_EXPECT_SERVER_HELLO -> State.CLIENT_EXPECT_ENCRYPTED_EXTENSIONS 2024-06-22 12:47:13,550 DEBUG quic [5af065b80f12c352] TLS State.CLIENT_EXPECT_ENCRYPTED_EXTENSIONS -> State.CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE 2024-06-22 12:47:13,550 DEBUG quic [5af065b80f12c352] Discarding epoch Epoch.INITIAL 2024-06-22 12:47:13,551 DEBUG quic [5af065b80f12c352] TLS State.CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE -> State.CLIENT_EXPECT_CERTIFICATE_VERIFY 2024-06-22 12:47:13,564 DEBUG quic [5af065b80f12c352] TLS State.CLIENT_EXPECT_CERTIFICATE_VERIFY -> State.CLIENT_EXPECT_FINISHED 2024-06-22 12:47:13,564 DEBUG quic [5af065b80f12c352] TLS State.CLIENT_EXPECT_FINISHED -> State.CLIENT_POST_HANDSHAKE 2024-06-22 12:47:13,564 INFO quic [5af065b80f12c352] ALPN negotiated protocol h3 2024-06-22 12:47:13,565 DEBUG quic [5af065b80f12c352] Stream 3 created by peer 2024-06-22 12:47:13,565 DEBUG quic [5af065b80f12c352] Stream 7 created by peer 2024-06-22 12:47:13,565 DEBUG quic [5af065b80f12c352] Stream 11 created by peer 2024-06-22 12:47:13,588 DEBUG quic [5af065b80f12c352] Discarding epoch Epoch.HANDSHAKE 2024-06-22 12:47:13,659 DEBUG quic [5af065b80f12c352] Stream 0 discarded 2024-06-22 12:47:13,659 INFO client Response received for GET / : 22268 bytes in 0.1 s (1.901 Mbps) 2024-06-22 12:47:13,659 INFO quic [5af065b80f12c352] Connection close sent (code 0x100, reason ) 2024-06-22 12:47:13,660 DEBUG quic [5af065b80f12c352] QuicConnectionState.CONNECTED -> QuicConnectionState.CLOSING 2024-06-22 12:47:13,922 DEBUG quic [5af065b80f12c352] Discarding epoch Epoch.ONE_RTT 2024-06-22 12:47:13,922 DEBUG quic [5af065b80f12c352] QuicConnectionState.CLOSING -> QuicConnectionState.TERMINATED which looks fairly successful. If I pick an example which has a v6 address (again testing that it works with curl --http3 and does use quic), it's not so good though: $ python3 examples/http3_client.py -v https://nghttp2.org/ 2024-06-22 12:51:37,231 DEBUG asyncio Using selector: KqueueSelector 2024-06-22 12:51:37,387 DEBUG quic [6a973bf9427e3818] TLS State.CLIENT_HANDSHAKE_START -> State.CLIENT_EXPECT_SERVER_HELLO 2024-06-22 12:51:37,592 DEBUG quic [6a973bf9427e3818] Loss detection triggered 2024-06-22 12:51:37,592 DEBUG quic [6a973bf9427e3818] Scheduled CRYPTO data for retransmission 2024-06-22 12:51:38,002 DEBUG quic [6a973bf9427e3818] Loss detection triggered 2024-06-22 12:51:38,002 DEBUG quic [6a973bf9427e3818] Scheduled CRYPTO data for retransmission 2024-06-22 12:51:38,812 DEBUG quic [6a973bf9427e3818] Loss detection triggered 2024-06-22 12:51:38,812 DEBUG quic [6a973bf9427e3818] Scheduled CRYPTO data for retransmission ^C2024-06-22 12:51:40,422 INFO quic [6a973bf9427e3818] Connection close sent (code 0x0, reason ) 2024-06-22 12:51:40,422 DEBUG quic [6a973bf9427e3818] QuicConnectionState.FIRSTFLIGHT -> QuicConnectionState.CLOSING 2024-06-22 12:51:41,032 DEBUG quic [6a973bf9427e3818] Discarding epoch Epoch.INITIAL 2024-06-22 12:51:41,032 DEBUG quic [6a973bf9427e3818] Discarding epoch Epoch.HANDSHAKE 2024-06-22 12:51:41,032 DEBUG quic [6a973bf9427e3818] Discarding epoch Epoch.ONE_RTT 2024-06-22 12:51:41,032 DEBUG quic [6a973bf9427e3818] QuicConnectionState.CLOSING -> QuicConnectionState.TERMINATED 2024-06-22 12:51:41,033 ERROR asyncio Future exception was never retrieved future: <Future finished exception=ConnectionError()>
Index: src/aioquic/asyncio/client.py --- src/aioquic/asyncio/client.py.orig +++ src/aioquic/asyncio/client.py @@ -1,5 +1,6 @@ import asyncio import socket +import sys from contextlib import asynccontextmanager from typing import AsyncGenerator, Callable, Optional, cast @@ -53,6 +54,9 @@ async def connect( infos = await loop.getaddrinfo(host, port, type=socket.SOCK_DGRAM) addr = infos[0][4] if len(addr) == 2: + if sys.platform.startswith('openbsd'): + local_host = "0.0.0.0" + else: addr = ("::ffff:" + addr[0], addr[1], 0, 0) # prepare QUIC connection @@ -67,15 +71,26 @@ async def connect( ) # explicitly enable IPv4/IPv6 dual stack - sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - completed = False - try: - sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - sock.bind((local_host, local_port, 0, 0)) - completed = True - finally: - if not completed: - sock.close() + if sys.platform.startswith('openbsd') and len(addr) == 2: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + completed = False + try: + sock.bind((local_host, local_port)) + completed = True + finally: + if not completed: + sock.close() + else: + sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + completed = False + try: + if not sys.platform.startswith('openbsd'): + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + sock.bind((local_host, local_port, 0, 0)) + completed = True + finally: + if not completed: + sock.close() # connect transport, protocol = await loop.create_datagram_endpoint( lambda: create_protocol(connection, stream_handler=stream_handler),
Index: src/aioquic/asyncio/client.py --- src/aioquic/asyncio/client.py.orig +++ src/aioquic/asyncio/client.py @@ -1,5 +1,6 @@ import asyncio import socket +import sys from contextlib import asynccontextmanager from typing import AsyncGenerator, Callable, Optional, cast @@ -53,6 +54,9 @@ async def connect( infos = await loop.getaddrinfo(host, port, type=socket.SOCK_DGRAM) addr = infos[0][4] if len(addr) == 2: + if sys.platform.startswith('openbsd'): + local_host = "0.0.0.0" + else: addr = ("::ffff:" + addr[0], addr[1], 0, 0) # prepare QUIC connection @@ -67,15 +71,26 @@ async def connect( ) # explicitly enable IPv4/IPv6 dual stack - sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) - completed = False - try: - sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - sock.bind((local_host, local_port, 0, 0)) - completed = True - finally: - if not completed: - sock.close() + if sys.platform.startswith('openbsd') and len(addr) == 2: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + completed = False + try: + sock.bind((local_host, local_port)) + completed = True + finally: + if not completed: + sock.close() + else: + sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + completed = False + try: + if not sys.platform.startswith('openbsd'): + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + sock.bind((local_host, local_port, 0, 0)) + completed = True + finally: + if not completed: + sock.close() # connect transport, protocol = await loop.create_datagram_endpoint( lambda: create_protocol(connection, stream_handler=stream_handler),