Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
959cd4c
everything build
danzh1989 Aug 7, 2024
4b969c9
Merge branch 'main' into networkchange
danzh1989 Aug 8, 2024
242dc5e
finish plumbing
danzh1989 Aug 8, 2024
28e805a
add tests
danzh1989 Aug 20, 2024
a582d79
comments
danzh1989 Aug 20, 2024
1e57860
add runtime guard
danzh1989 Aug 21, 2024
e3663b2
change log
danzh1989 Aug 21, 2024
ccd0dd2
fix build
danzh1989 Aug 21, 2024
7e8e3d3
fix build again
danzh1989 Aug 21, 2024
84eeb68
more fix
danzh1989 Aug 21, 2024
2337129
fix compile option build
danzh1989 Aug 22, 2024
9725172
Merge branch 'main' into networkchange
danzh1989 Aug 22, 2024
845c19d
test coverage
danzh1989 Aug 22, 2024
462e7bd
fix build
danzh1989 Aug 22, 2024
76e0894
fix compile option build
danzh1989 Aug 23, 2024
e47c408
Merge branch 'main' into networkchange
danzh1989 Aug 27, 2024
c239b59
Merge branch 'main' into networkchange
danzh1989 Sep 4, 2024
2bf31a3
more merge fix
danzh1989 Sep 4, 2024
27058bc
comment
danzh1989 Sep 4, 2024
fe5b171
Merge branch 'main' into networkchange
danzh1989 Sep 6, 2024
2d78be5
new callback interface
danzh1989 Sep 7, 2024
f883a1c
Merge branch 'main' into networkchange
danzh1989 Sep 7, 2024
2af5cfb
dedup dispatcher post
danzh1989 Sep 11, 2024
471b7bb
Merge branch 'main' into networkchange
danzh1989 Sep 11, 2024
5ad0e26
fix unit test
danzh1989 Sep 11, 2024
ddd3739
revert some changes
danzh1989 Sep 8, 2025
c2c3909
Merge branch 'main' into networkchange
danzh1989 Sep 9, 2025
225d362
revert release note
danzh1989 Sep 9, 2025
eae7c9e
Merge branch 'main' into networkchange
danzh1989 Sep 12, 2025
ee6a0b4
observer interface change
danzh1989 Sep 12, 2025
bd3fd65
more interface changes
danzh1989 Sep 12, 2025
6815335
Merge branch 'main' into networkchange
danzh1989 Oct 3, 2025
cdc290d
revert transient connect failure handling
danzh1989 Oct 3, 2025
f668a04
remove quic_network_connectivity_observer_impl_lib
danzh1989 Oct 3, 2025
73d0b0e
build target
danzh1989 Oct 3, 2025
47d708f
revert tests
danzh1989 Oct 9, 2025
8c576fa
revert upstream BUILD
danzh1989 Oct 9, 2025
6ab8b05
revert mobile changes
danzh1989 Oct 9, 2025
3dba5e6
a disconnect interface
danzh1989 Oct 9, 2025
809ee7e
revert runtime
danzh1989 Oct 9, 2025
7c31d1e
fix build
danzh1989 Oct 9, 2025
f22a3d9
test coverage
danzh1989 Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ minor_behavior_changes:
change: |
HTTP/3 alt-svc headers will now be respected from IP-address-based hostnames. This change is
guarded by runtime guard ``envoy.reloadable_features.allow_alt_svc_for_ips``.
- area: http3
Comment thread
danzh2010 marked this conversation as resolved.
change: |
Make upstream connections respond to default network change on mobile devices. This change is guarded
by runtime guard ``envoy.reloadable_features.quic_upstream_connection_handle_network_change``.
- area: lua
change: |
When Lua script executes httpCall, backpressure is exercised when receiving body from downstream client. This behavior can be reverted
Expand Down
19 changes: 19 additions & 0 deletions envoy/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,19 @@ class ServerConnection : public virtual Connection {

using ServerConnectionPtr = std::unique_ptr<ServerConnection>;

/**
* Used by a client connection to report any changes to its underlying network.
*/
class NetworkChangeCallbacks {
public:
virtual ~NetworkChangeCallbacks() = default;

/**
* Called by a connection if its underlying network has changed.
*/
virtual void onConnectionNetworkChanged() PURE;
};

/**
* Connections capable of outbound connects.
*/
Expand All @@ -423,6 +436,12 @@ class ClientConnection : public virtual Connection {
* registered via addConnectionCallbacks().
*/
virtual void connect() PURE;

/**
* Register network change callbacks to be called when the underlying network has changed.
* @param callback must outlive this connection.
*/
virtual void setNetworkChangeCallbacks(NetworkChangeCallbacks& callback) PURE;
};

using ClientConnectionPtr = std::unique_ptr<ClientConnection>;
Expand Down
2 changes: 1 addition & 1 deletion mobile/library/common/internal_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ void InternalEngine::onDefaultNetworkAvailable() {
void InternalEngine::onDefaultNetworkChanged(NetworkType network) {
ENVOY_LOG_MISC(trace, "Calling the default network changed callback");
dispatcher_->post([&, network]() -> void {
envoy_netconf_t configuration = Network::ConnectivityManagerImpl::setPreferredNetwork(network);
envoy_netconf_t configuration = connectivity_manager_->onNetworkMadeDefault(network);
if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.dns_cache_set_ip_version_to_remove")) {
// The IP version to remove flag must be set first before refreshing the DNS cache so that
Expand Down
11 changes: 11 additions & 0 deletions mobile/library/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ licenses(["notice"]) # Apache 2

envoy_mobile_package()

envoy_cc_library(
name = "envoy_mobile_quic_network_observer_registry_factory_lib",
srcs = ["envoy_mobile_quic_network_observer_registry_factory.cc"],
hdrs = ["envoy_mobile_quic_network_observer_registry_factory.h"],
repository = "@envoy",
deps = [
"@envoy//source/common/quic:envoy_quic_client_session_lib",
],
)

envoy_cc_library(
name = "connectivity_manager_lib",
srcs = [
Expand All @@ -14,6 +24,7 @@ envoy_cc_library(
],
repository = "@envoy",
deps = [
":envoy_mobile_quic_network_observer_registry_factory_lib",
":proxy_settings_lib",
"//library/common:engine_types_lib",
"//library/common/network:src_addr_socket_option_lib",
Expand Down
35 changes: 27 additions & 8 deletions mobile/library/common/network/connectivity_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ constexpr unsigned int InitialFaultThreshold = 1;
// L7 bytes) before switching socket mode.
constexpr unsigned int MaxFaultThreshold = 3;

ConnectivityManagerImpl::ConnectivityManagerImpl(Upstream::ClusterManager& cluster_manager,
DnsCacheManagerSharedPtr dns_cache_manager)
: cluster_manager_(cluster_manager), dns_cache_manager_(dns_cache_manager) {
if (quic_upstream_connection_handle_network_change_) {
cluster_manager_.createNetworkObserverRegistries(quic_observer_registry_factory_);
}
}

ConnectivityManagerImpl::NetworkState ConnectivityManagerImpl::network_state_{
1, NetworkType::Generic, MaxFaultThreshold, SocketMode::DefaultPreferredNetworkMode,
Thread::MutexBasicLockable{}};
Expand All @@ -102,6 +110,16 @@ envoy_netconf_t ConnectivityManagerImpl::setPreferredNetwork(NetworkType network
return network_state_.configuration_key_;
}

envoy_netconf_t ConnectivityManagerImpl::onNetworkMadeDefault(NetworkType network) {
ENVOY_LOG_MISC(trace, "Default network changed to {}", static_cast<int>(network));
envoy_netconf_t configuration_key = setPreferredNetwork(network);
for (std::reference_wrapper<Quic::EnvoyMobileQuicNetworkObserverRegistry> registry :
quic_observer_registry_factory_.getCreatedObserverRegistries()) {
registry.get().onNetworkMadeDefault();
}
return configuration_key;
}

void ConnectivityManagerImpl::setProxySettings(ProxySettingsConstSharedPtr new_proxy_settings) {
if (proxy_settings_ == nullptr && new_proxy_settings != nullptr) {
ENVOY_LOG_EVENT(info, "netconf_proxy_change", "{}", new_proxy_settings->asString());
Expand Down Expand Up @@ -305,16 +323,17 @@ Socket::OptionsSharedPtr ConnectivityManagerImpl::getUpstreamSocketOptions(Netwo
network != NetworkType::Generic) {
return getAlternateInterfaceSocketOptions(network);
}

// Envoy uses the hash signature of overridden socket options to choose a connection pool.
// Setting a dummy socket option is a hack that allows us to select a different
// connection pool without materially changing the socket configuration.
ASSERT(static_cast<int>(network) >= 0 && static_cast<int>(network) < 3);
int ttl_value = DEFAULT_IP_TTL + static_cast<int>(network);
auto options = std::make_shared<Socket::Options>();
options->push_back(std::make_shared<AddrFamilyAwareSocketOptionImpl>(
envoy::config::core::v3::SocketOption::STATE_PREBIND, ENVOY_SOCKET_IP_TTL,
ENVOY_SOCKET_IPV6_UNICAST_HOPS, ttl_value));
if (!quic_upstream_connection_handle_network_change_) {

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.

Why are we not doing this when the runtime guard is true?

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.

This is the old way of not reusing the existing connections for new request. It actually changes how a request is hashed to a connection pool. Since we are going away connections, we don't need to rely on this. And in the new world, we should still be able to reuse the old connection pools, just not the existing connection in it.

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.

Ah, I see. Good point. Nice!

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.

so I agree with this as a long term plan but without marking H2 and H1 connections as draining won't this possibly be a regression for non-H3 traffic?

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.

That's a good point! I think this feature needs to be enabled when both H2 and H3 support network change.

Adding network connectivity observers to H2 is another big change. I would prefer not to do it in this PR. The runtime guard is default false, do you think we can land the H3 support first and then change H2 and flip the runtime guard?

I didn't see how H1 is handled in Chromium. Is network change an issue in H1 at all?

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.

For H1 in Chrome, I think the connections are simply abruptly terminated when the IP address changes:

https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_stream_pool.cc;l=257;drc=8e948282d37c0e119e3102236878d6f4d5052c16

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.

Filed #36059 for tracking and explanation.

// Envoy uses the hash signature of overridden socket options to choose a connection pool.
// Setting a dummy socket option is a hack that allows us to select a different
// connection pool without materially changing the socket configuration.
int ttl_value = DEFAULT_IP_TTL + static_cast<int>(network);
options->push_back(std::make_shared<AddrFamilyAwareSocketOptionImpl>(
envoy::config::core::v3::SocketOption::STATE_PREBIND, ENVOY_SOCKET_IP_TTL,
ENVOY_SOCKET_IPV6_UNICAST_HOPS, ttl_value));
}
return options;
}

Expand Down
16 changes: 14 additions & 2 deletions mobile/library/common/network/connectivity_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h"

#include "library/common/engine_types.h"
#include "library/common/network/envoy_mobile_quic_network_observer_registry_factory.h"
#include "library/common/network/proxy_settings.h"
#include "library/common/types/c_types.h"

Expand Down Expand Up @@ -186,6 +187,13 @@ class ConnectivityManager
* @returns the default DNS cache set up in base configuration or nullptr.
*/
virtual Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr dnsCache() PURE;

/**
* Called when the OS changes the preferred network.
* @param network, the OS-preferred network.
* @returns configuration key of the latest snapshot of network configuration state.
*/
virtual envoy_netconf_t onNetworkMadeDefault(NetworkType network) PURE;
};

class ConnectivityManagerImpl : public ConnectivityManager,
Expand All @@ -201,8 +209,7 @@ class ConnectivityManagerImpl : public ConnectivityManager,
static envoy_netconf_t setPreferredNetwork(NetworkType network);

ConnectivityManagerImpl(Upstream::ClusterManager& cluster_manager,
DnsCacheManagerSharedPtr dns_cache_manager)
: cluster_manager_(cluster_manager), dns_cache_manager_(dns_cache_manager) {}
DnsCacheManagerSharedPtr dns_cache_manager);

// Extensions::Common::DynamicForwardProxy::DnsCache::UpdateCallbacks
void onDnsHostAddOrUpdate(
Expand Down Expand Up @@ -232,6 +239,7 @@ class ConnectivityManagerImpl : public ConnectivityManager,
SocketMode socket_mode) override;
envoy_netconf_t addUpstreamSocketOptions(Socket::OptionsSharedPtr options) override;
Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr dnsCache() override;
envoy_netconf_t onNetworkMadeDefault(NetworkType network) override;

private:
struct NetworkState {
Expand All @@ -252,9 +260,13 @@ class ConnectivityManagerImpl : public ConnectivityManager,
Extensions::Common::DynamicForwardProxy::DnsCache::AddUpdateCallbacksHandlePtr
dns_callbacks_handle_{nullptr};
Upstream::ClusterManager& cluster_manager_;
Quic::EnvoyMobileQuicNetworkObserverRegistryFactory quic_observer_registry_factory_;
DnsCacheManagerSharedPtr dns_cache_manager_;
ProxySettingsConstSharedPtr proxy_settings_;
static NetworkState network_state_;
// If true, all QUIC connections will respond to network changes.
const bool quic_upstream_connection_handle_network_change_{Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.quic_upstream_connection_handle_network_change")};
};

using ConnectivityManagerSharedPtr = std::shared_ptr<ConnectivityManager>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "library/common/network/envoy_mobile_quic_network_observer_registry_factory.h"

#include "source/common/runtime/runtime_features.h"

namespace Envoy {
namespace Quic {

void EnvoyMobileQuicNetworkObserverRegistry::onNetworkMadeDefault() {
ENVOY_LOG_MISC(trace, "Default network changed.");
ASSERT(dispatcher_.isThreadSafe());
ASSERT(Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.quic_upstream_connection_handle_network_change"));
// Retain the existing observers in a list and iterate on the list as new
// connections might be created and registered during iteration.
std::vector<QuicNetworkConnectivityObserver*> existing_observers;
existing_observers.reserve(registeredQuicObservers().size());
for (QuicNetworkConnectivityObserver* observer : registeredQuicObservers()) {
existing_observers.push_back(observer);
}
for (QuicNetworkConnectivityObserver* observer : existing_observers) {
observer->onNetworkChanged();
}
}

EnvoyQuicNetworkObserverRegistryPtr
EnvoyMobileQuicNetworkObserverRegistryFactory::createQuicNetworkObserverRegistry(
Event::Dispatcher& dispatcher) {
auto result = std::make_unique<EnvoyMobileQuicNetworkObserverRegistry>(dispatcher);
thread_local_observer_registries_.emplace_back(*result);
return result;
}

} // namespace Quic
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

#include "source/common/quic/envoy_quic_network_observer_registry_factory.h"
#include "source/common/quic/quic_network_connectivity_observer.h"

namespace Envoy {
namespace Quic {

// A mobile implementation that also handles network change events.
class EnvoyMobileQuicNetworkObserverRegistry : public EnvoyQuicNetworkObserverRegistry {
public:
explicit EnvoyMobileQuicNetworkObserverRegistry(Event::Dispatcher& dispatcher)
: dispatcher_(dispatcher) {}

// Called when the default network has changed to notify each registered observer.
void onNetworkMadeDefault();
Comment thread
danzh2010 marked this conversation as resolved.
Outdated

private:
Event::Dispatcher& dispatcher_;
};

class EnvoyMobileQuicNetworkObserverRegistryFactory
: public EnvoyQuicNetworkObserverRegistryFactory {
public:
EnvoyMobileQuicNetworkObserverRegistryFactory() { thread_local_observer_registries_.reserve(1); }

EnvoyQuicNetworkObserverRegistryPtr
createQuicNetworkObserverRegistry(Event::Dispatcher& dispatcher) override;

std::vector<std::reference_wrapper<EnvoyMobileQuicNetworkObserverRegistry>>&
getCreatedObserverRegistries() {
return thread_local_observer_registries_;
}

private:
std::vector<std::reference_wrapper<EnvoyMobileQuicNetworkObserverRegistry>>
thread_local_observer_registries_;
};

using EnvoyMobileQuicNetworkObserverRegistryPtr =
std::unique_ptr<EnvoyMobileQuicNetworkObserverRegistry>;

} // namespace Quic
} // namespace Envoy
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class MockConnectivityManager : public Network::ConnectivityManager {
const Extensions::Common::DynamicForwardProxy::DnsHostInfoSharedPtr&,
Network::DnsResolver::ResolutionStatus));
MOCK_METHOD(Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr, dnsCache, ());
MOCK_METHOD(envoy_netconf_t, onNetworkMadeDefault, (NetworkType network));
};

class NetworkConfigurationFilterTest : public testing::Test {
Expand Down
11 changes: 8 additions & 3 deletions mobile/test/common/integration/client_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1334,15 +1334,20 @@ TEST_P(ClientIntegrationTest, TestProxyResolutionApi) {
}
#endif

// This test is simply to test the IPv6 connectivity check and DNS refresh and make sure the code
// doesn't crash. It doesn't really test the actual network change event.
// Tests the IPv6 connectivity check and DNS refresh happen upon network change. And idle HTTP/3
// connections get closed.
TEST_P(ClientIntegrationTest, OnNetworkChanged) {
builder_.addRuntimeGuard("dns_cache_set_ip_version_to_remove", true);
builder_.addRuntimeGuard("quic_upstream_connection_handle_network_change", true);
initialize();
internalEngine()->onDefaultNetworkChanged(NetworkType::WLAN);
basicTest();
internalEngine()->onDefaultNetworkChanged(NetworkType::WLAN);
if (upstreamProtocol() == Http::CodecType::HTTP1) {
ASSERT_EQ(cc_.on_complete_received_byte_count_, 67);
} else if (upstreamProtocol() == Http::CodecType::HTTP3) {
ASSERT_TRUE(waitForCounterGe("http3.upstream.tx.quic_connection_close_error_code_QUIC_"
"CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS",
1));
}
}

Expand Down
19 changes: 19 additions & 0 deletions mobile/test/common/network/connectivity_manager_test.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <net/if.h>

#include "test/extensions/common/dynamic_forward_proxy/mocks.h"
#include "test/mocks/event/mocks.h"
#include "test/mocks/upstream/cluster_manager.h"

#include "gtest/gtest.h"
Expand Down Expand Up @@ -48,6 +49,24 @@ TEST_F(ConnectivityManagerTest,
EXPECT_EQ(original_key, connectivity_manager_->getConfigurationKey());
}

TEST_F(ConnectivityManagerTest, OnNetworkMadeDefault) {
Runtime::maybeSetRuntimeGuard(
"envoy.reloadable_features.quic_upstream_connection_handle_network_change", true);
Quic::EnvoyMobileQuicNetworkObserverRegistryPtr registry;
Event::MockDispatcher dispatcher;
EXPECT_CALL(cm_, createNetworkObserverRegistries(_))
.WillOnce(Invoke([&](Quic::EnvoyQuicNetworkObserverRegistryFactory& factory) {
registry.reset(static_cast<Quic::EnvoyMobileQuicNetworkObserverRegistry*>(
factory.createQuicNetworkObserverRegistry(dispatcher).release()));
}));
connectivity_manager_ = std::make_shared<ConnectivityManagerImpl>(cm_, dns_cache_manager_);
EXPECT_NE(nullptr, registry);
envoy_netconf_t original_key = connectivity_manager_->getConfigurationKey();
EXPECT_CALL(dispatcher, isThreadSafe());
envoy_netconf_t new_key = connectivity_manager_->onNetworkMadeDefault(NetworkType::WWAN);
EXPECT_NE(original_key, new_key);
}

TEST_F(ConnectivityManagerTest, RefreshDnsForCurrentConfigurationTriggersDnsRefresh) {
EXPECT_CALL(*dns_cache_, forceRefreshHosts());
envoy_netconf_t configuration_key = connectivity_manager_->getConfigurationKey();
Expand Down
Loading
Loading