> 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),

Reply via email to