envoyproxy / envoyproxy/envoy

IPv4 destinations on dual-stack IPv6 sockets fail on macOS

Open
#46,506 7 comments 0 reactions 0 assignees View on GitHub
area/macos
Dominant language
C++
Stars
28.9k
Forks
5.6k
Avg merge
1d 20h
Merged PRs (30d)
437

Description

*Description*:

`IoSocketHandleImpl::connect()`/`sendmsg()` pass a `sockaddr_in` to `AF_INET6` sockets for `AF_INET` destinations. Fails on macOS, works on Linux. The v4-mapped `sockaddr_in6` form works everywhere.

Doesn't concern any client code, client sockets take their family from the address they're created with, so the families never diverge. It only arises on dual-stack listeners, the peer address arrives v4-mapped and `addressFromSockAddr()` normalizes it to `Ipv4Instance` because `socket_v6only_` is false, so the reply goes back out on that AF_INET6 fd with a `sockaddr_in`.

Tested some things over loopback [here](https://gist.github.com/filipcacky/f2af5a4eab084db5b8cd0a772015a135), didn't check on real network, so might be different.

| Operation | V6ONLY | Debian 6.12 | macOS 26.5 Darwin 25.5.0 | iOS 26.5.2 (23F84) |
|--------------------------------------------|------------|----------------------|--------------------------|-------------------------|
| TCP connect, sockaddr_in | dual-stack | EINVAL | EAFNOSUPPORT | EPERM |
| UDP connect, sockaddr_in | dual-stack | OK | EINVAL | EPERM |
| UDP sendto, sockaddr_in | dual-stack | OK | EINVAL | EPERM |
| TCP connect, v4-mapped sockaddr_in6 | 1 | ENETUNREACH | EAFNOSUPPORT | EAFNOSUPPORT |
| UDP connect + send, v4-mapped sockaddr_in6 | 1 | connect ENETUNREACH | connect OK, send EINVAL | connect OK, send EINVAL |
| UDP sendto, v4-mapped sockaddr_in6 | 1 | ENETUNREACH | OK | OK |

The UDP sendto to v4-mapped on V6ONLY doesn't concern this, but it's _interesting_.

*Reproduction*:

- QuicHttpIntegrationSPATest.UsesPreferredAddressDualStack
```
[ RUN ] QuicHttpIntegrationSPATests/QuicHttpIntegrationSPATest.UsesPreferredAddressDualStack/IPv4_quiche_client_impl_migration_in_house
[error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[error][quic] [external/quiche/quiche/quic/core/quic_connection.cc:4093] Server: Write failed with error: 22 (Invalid argument)
[error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[error][testing] [./test/integration/quic_http_integration_test.h:330] Fail to connect to server with error: QUIC_NETWORK_IDLE_TIMEOUT with details: No recent network activity after 4002631us. Timeout:4s num_undecryptable_packets: 0 {}
[error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
...
```

- UDP proxy
Dual-stack listener on `[::]` + `ipv4_compat`, v4 client gets no reply. Datagram is forwarded upstream fine, the reply `sendmsg()` on the v6 fd fails with:
```
[debug][filter] [source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc:662] creating new session: downstream=127.0.0.1:52645 local=127.0.0.1:10000 upstream=127.0.0.1:10001
[error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[debug][misc] [source/common/network/utility.cc:527] sendmsg failed with error code 11: Invalid argument
```

Contributor guide

Open the contributing guide

Research direction

Start in source/common/network/io_socket_handle_impl.cc at IoSocketHandleImpl::connect(), sendmsg(), and addressFromSockAddr(). Run QuicHttpIntegrationSPATest.UsesPreferredAddressDualStack and reproduce the UDP proxy case on macOS. Done means IPv4 destinations sent through dual-stack IPv6 sockets no longer fail with EINVAL or EAFNOSUPPORT.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, macos
Domain
networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.