Skip to content

test/integration: Fix quic_http_integration_test on macOS#45318

Open
filipcacky wants to merge 5 commits into
envoyproxy:mainfrom
filipcacky:macos_quic_integration
Open

test/integration: Fix quic_http_integration_test on macOS#45318
filipcacky wants to merge 5 commits into
envoyproxy:mainfrom
filipcacky:macos_quic_integration

Conversation

@filipcacky

@filipcacky filipcacky commented May 27, 2026

Copy link
Copy Markdown
Contributor

Commit Message: test/integration: Fix Linux assumptions failing quic_http_integration_test on macOS

QuicListenerFilterReceivesFirstPacketWithCmsg hardcoded IP_TOS, IP_RECVTOS and IPV6_PKTINFO Linux values which caused the test to fail on macOS.

UsesPreferredAddress{,DNAT,DualStack} assumed 127.0.0.2 to be bindable, which is not the case on macOS by default.

Risk Level: Low
Testing: Fixed/skipped tests for macOS, passes on Linux
Docs Changes: N/A
Release Notes: N/A
Platform Specific Features: N/A

…istenerFilterReceivesFirstPacketWithCmsg`

Signed-off-by: Filip Cacky <filip.cacky@cdn77.com>
UsesPreferredAddress{,DNAT,DualStack} configure 127.0.0.2 as the server's
preferred address. This requires 127.0.0.2 to be configured on a
local interface, which is not the case on macOS by default.

Probe at the start of each test by attempting to bind a UDP socket
to 127.0.0.2 and skip when bind fails, instead of letting the tests
fail. On Linux the entire 127/8 range is reachable, so the probe
always passes.

Signed-off-by: Filip Cacky <filip.cacky@cdn77.com>
@repokitteh-read-only

Copy link
Copy Markdown

Hi @filipcacky, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #45318 was opened by filipcacky.

see: more, trace.

@filipcacky filipcacky had a problem deploying to external-contributors May 27, 2026 16:40 — with GitHub Actions Error
@filipcacky

Copy link
Copy Markdown
Contributor Author

Even with ifconfig lo0 alias 127.0.0.2 UsesPreferredAddressDualStack still fails with:

[ RUN      ] QuicHttpIntegrationSPATests/QuicHttpIntegrationSPATest.UsesPreferredAddressDualStack/IPv4_all_clients_impl_migration_by_quiche
[2026-05-27 16:44:16.010][71063360][error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[2026-05-27 16:44:16.010][71063360][error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[2026-05-27 16:44:16.010][71063360][error][quic] [external/quiche/quiche/quic/core/quic_connection.cc:4096] Server: Write failed with error: 22 (Invalid argument)
[2026-05-27 16:44:16.304][71063360][error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[2026-05-27 16:44:16.304][71063360][error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[2026-05-27 16:44:16.906][71063360][error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[2026-05-27 16:44:16.906][71063360][error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
[2026-05-27 16:44:20.004][71060470][error][testing] [./test/integration/quic_http_integration_test.h:326] Fail to connect to server with error: QUIC_NETWORK_IDLE_TIMEOUT with details: No recent network activity after 4002328us. Timeout:4s num_undecryptable_packets: 0 {}
[2026-05-27 16:44:20.004][71063360][error][io] [source/common/network/io_socket_handle_impl.cc:227] EINVAL error. Socket is open: true, IPv4.
[2026-05-27 16:44:20.004][71063360][error][io] [./source/common/network/io_socket_handle_impl.h:91] Invalid argument passed in.
test/integration/http_integration.cc:320: Failure
Value of: codec->connected()
  Actual: false
Expected: true
QUIC_NETWORK_IDLE_TIMEOUT with details: No recent network activity after 4002328us. Timeout:4s num_undecryptable_packets: 0 {}
Stack trace:
  0x1042bb90c: Envoy::HttpIntegrationTest::makeHttpConnection()
  0x1040f5450: Envoy::Quic::QuicHttpIntegrationSPATest_UsesPreferredAddressDualStack_Test::TestBody()
  0x10a11cf28: testing::internal::HandleSehExceptionsInMethodIfSupported<>()
  0x10a0e6c6c: testing::internal::HandleExceptionsInMethodIfSupported<>()
  0x10a0e6bbc: testing::Test::Run()
  0x10a0e79d4: testing::TestInfo::Run()
... Google Test internal frames ...

test/integration/quic_http_integration_test.cc:1814: Failure
Value of: quic_session->received_ipv4_alternate_server_address().has_value()
  Actual: false
Expected: true
Stack trace:
  0x1040f574c: Envoy::Quic::QuicHttpIntegrationSPATest_UsesPreferredAddressDualStack_Test::TestBody()
  0x10a11cf28: testing::internal::HandleSehExceptionsInMethodIfSupported<>()
  0x10a0e6c6c: testing::internal::HandleExceptionsInMethodIfSupported<>()
  0x10a0e6bbc: testing::Test::Run()
  0x10a0e79d4: testing::TestInfo::Run()
... Google Test internal frames ...

[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_quic_always_support_server_preferred_address to: true
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_reloadable_features_no_extension_lookup_by_name to: true
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_reloadable_features_quic_send_server_preferred_address_to_all_clients to: true
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_reloadable_features_runtime_initialized to: false
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_quic_always_support_server_preferred_address to: true
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_reloadable_features_no_extension_lookup_by_name to: true
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_reloadable_features_quic_send_server_preferred_address_to_all_clients to: true
[external/abseil-cpp/absl/flags/internal/flag.cc : 148] RAW: Restore saved value of envoy_reloadable_features_runtime_initialized to: false
[  FAILED  ] QuicHttpIntegrationSPATests/QuicHttpIntegrationSPATest.UsesPreferredAddressDualStack/IPv4_all_clients_impl_migration_by_quiche, where GetParam() = (4-byte object <00-00 00-00>, true, true) (4353 ms)

and probably needs something like https://gist.github.com/filipcacky/ffbbae44c0b10ba3c07b4a7f537887cb, but then it fails on something else. Didn't dig too much into it yet.

@filipcacky filipcacky force-pushed the macos_quic_integration branch 2 times, most recently from 8e7962d to e9a3c89 Compare June 18, 2026 08:18
macOS rejects sendmsg with EINVAL when msg_name is AF_INET on an
AF_INET6 fd, even when the socket is dual-stack. This breaks QUIC SPA
replies on a dual-stack listener: PATH_RESPONSE never leaves the
socket. Linux accepts both forms.

Signed-off-by: Filip Cacky <filip.cacky@cdn77.com>
@botengyao

Copy link
Copy Markdown
Member

@danzh2010, are you up for a review?

@danzh2010

Copy link
Copy Markdown
Contributor

Commit Message: test/integration: Fix quic_http_integration_test on macOS Additional Description:

QuicListenerFilterReceivesFirstPacketWithCmsg hardcoded IP_TOS, IP_RECVTOS and IPV6_PKTINFO Linux values which caused the test to fail on macOS.

UsesPreferredAddress{,DNAT,DualStack} assumed 127.0.0.2 to be bindable, which is not the case on macOS by default.

UsesPreferredAddressDualStack failed with EINVAL when sendmsg received AF_INET msg_name instead of AF_INET6, which is rejected by macOS.

Risk Level: Low Testing: Fixed tests for macOS, passes on Linux Docs Changes: None Release Notes: None Platform Specific Features: sendmsg on APPLE now passes AF_INET6 msg_name instead of AF_INET for dual-stack SPA quic listeners.

This is not a test-only change. The change to sendmsg() applies everywhere on iOS and MacOS.

Comment on lines +182 to +183
const auto* v4 = dynamic_cast<const Address::Ipv4Instance*>(&peer_address);
if (v4 != nullptr) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check version() instead of relying on dynamic_cast.

// macOS rejects an AF_INET msg_name on an AF_INET6 fd with EINVAL even when the
// socket is dual-stack. When sending an IPv4 destination on such a socket, swap msg_name
// for the v4-mapped sockaddr_in6 form.
sockaddr_in6 mapped_peer;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tmp variable can be placed inside the if block below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to outlive the if block because of message.msg_name = &mapped_peer;

Comment thread source/common/network/address_impl.cc Outdated
@@ -178,6 +178,17 @@ bool Ipv4Instance::operator==(const Instance& rhs) const {
(ip_.port() == rhs_casted->ip_.port()) && (networkNamespace() == rhs.networkNamespace()));
}

sockaddr_in6 Ipv4Instance::v4MappedSockAddr() const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the v4 mapped address isn't retained, this method isn't necessary. You can just put this logic into a helper function of sendmsg().

}
}
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits: for the whole block you can write:

#ifndef __APPLE__
  message.msg_name = reinterpret_cast<void*>(sock_addr);
  message.msg_namelen = address_base->sockAddrLen();
#else
  // populate with mapped address 
#endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  message.msg_name = reinterpret_cast<void*>(sock_addr);
  message.msg_namelen = address_base->sockAddrLen();

still needs to run if !(domain_ == AF_INET6 && sock_addr->sa_family == AF_INET), so i'd still need to put that in else branch.

@@ -172,6 +172,22 @@ Api::IoCallUint64Result IoSocketHandleImpl::sendmsg(const Buffer::RawSlice* slic
msghdr message;
message.msg_name = reinterpret_cast<void*>(sock_addr);
message.msg_namelen = address_base->sockAddrLen();

#if defined(__APPLE__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is __APPLE__ necessarily macOS? How about iOS behavior?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to check iOS behaviour. But seems like the way to check target for apple is using TargetConditionals.h

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are probably some macros to distinguish different apple platforms.

Actually we didn't notice any such write error in YT app on iOS but I would assume MacOS and iOS use the same network stack. Can you do some research about which apple platform has this issue?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about connect()? We also used connected UDP socket with writev().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't notice that, seems to have the same issue.

@RyanTheOptimist

Copy link
Copy Markdown
Contributor

Also needs a main merge
/wait

@filipcacky filipcacky left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connect has the same EINVAL issue for both TCP and UDP and i'm not sure yet how to handle this properly together with envoy.reloadable_features.always_use_v6.

I decided to revert the io handle changes, seems to be more complicated and out of scope of wanting to fix a few tests. I'll file a new PR with io handle tests, seems like there are none for this at the moment, so it manifests in other unit tests instead. Sorry for the extra work.

// macOS rejects an AF_INET msg_name on an AF_INET6 fd with EINVAL even when the
// socket is dual-stack. When sending an IPv4 destination on such a socket, swap msg_name
// for the v4-mapped sockaddr_in6 form.
sockaddr_in6 mapped_peer;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to outlive the if block because of message.msg_name = &mapped_peer;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't notice that, seems to have the same issue.

@@ -172,6 +172,22 @@ Api::IoCallUint64Result IoSocketHandleImpl::sendmsg(const Buffer::RawSlice* slic
msghdr message;
message.msg_name = reinterpret_cast<void*>(sock_addr);
message.msg_namelen = address_base->sockAddrLen();

#if defined(__APPLE__)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to check iOS behaviour. But seems like the way to check target for apple is using TargetConditionals.h

}
}
#endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  message.msg_name = reinterpret_cast<void*>(sock_addr);
  message.msg_namelen = address_base->sockAddrLen();

still needs to run if !(domain_ == AF_INET6 && sock_addr->sa_family == AF_INET), so i'd still need to put that in else branch.

This reverts commit 8638e35.

Signed-off-by: Filip Cacky <filip.cacky@cdn77.com>
Signed-off-by: Filip Cacky <filip.cacky@cdn77.com>
@filipcacky filipcacky force-pushed the macos_quic_integration branch from 64fbf6f to bd406d4 Compare July 3, 2026 12:22

@danzh2010 danzh2010 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants