-
Notifications
You must be signed in to change notification settings - Fork 5.5k
quic: change QuicNetworkConnectivityObserver interface #35775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
959cd4c
4b969c9
242dc5e
28e805a
a582d79
1e57860
e3663b2
ccd0dd2
7e8e3d3
84eeb68
2337129
9725172
845c19d
462e7bd
76e0894
e47c408
c239b59
2bf31a3
27058bc
fe5b171
2d78be5
f883a1c
2af5cfb
471b7bb
5ad0e26
ddd3739
c2c3909
225d362
eae7c9e
ee6a0b4
bd3fd65
6815335
cdc290d
f668a04
73d0b0e
47d708f
8c576fa
6ab8b05
3dba5e6
809ee7e
7c31d1e
f22a3d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,20 @@ | |
| #include "absl/container/node_hash_map.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| namespace Quic { | ||
|
|
||
| #ifdef ENVOY_ENABLE_QUIC | ||
| class EnvoyQuicNetworkObserverRegistryFactory; | ||
| class EnvoyQuicNetworkObserverRegistry; | ||
| #else | ||
| // Dumb definitions of QUIC classes if QUICHE is compiled out. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: QUICHE is always compiled in, but the QUIC parts may or may not be. So probably s/QUICHE/QUIC/? |
||
| class EnvoyQuicNetworkObserverRegistryFactory {}; | ||
| class EnvoyQuicNetworkObserverRegistry {}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think we should handle this conditional definition at the place where these classes are defined. Perhaps instead of conditionally building the .h/.cc files, we could wrap their contents in |
||
| #endif | ||
|
|
||
| } // namespace Quic | ||
|
|
||
| namespace Upstream { | ||
|
|
||
| /** | ||
|
|
@@ -467,6 +481,13 @@ class ClusterManager { | |
| * Returns an EdsResourcesCache that is unique for the cluster manager. | ||
| */ | ||
| virtual Config::EdsResourcesCacheOptRef edsResourcesCache() PURE; | ||
|
|
||
| /** | ||
| * Create a QUIC network observer registry for each worker thread using the given factory. | ||
| * @param factory used to create a registry object. | ||
| */ | ||
| virtual void createNetworkObserverRegistries( | ||
| Envoy::Quic::EnvoyQuicNetworkObserverRegistryFactory& factory) PURE; | ||
| }; | ||
|
|
||
| using ClusterManagerPtr = std::unique_ptr<ClusterManager>; | ||
|
|
@@ -515,6 +536,8 @@ class ClusterManagerFactory { | |
| /** | ||
| * Allocate an HTTP connection pool for the host. Pools are separated by 'priority', | ||
| * 'protocol', and 'options->hashKey()', if any. | ||
| * @param network_observer_registry if not null all the QUIC connections created by this pool | ||
| * should register to it for network events. | ||
| */ | ||
| virtual Http::ConnectionPool::InstancePtr | ||
| allocateConnPool(Event::Dispatcher& dispatcher, HostConstSharedPtr host, | ||
|
|
@@ -524,7 +547,8 @@ class ClusterManagerFactory { | |
| const Network::ConnectionSocket::OptionsSharedPtr& options, | ||
| const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, | ||
| TimeSource& time_source, ClusterConnectivityState& state, | ||
| Http::PersistentQuicInfoPtr& quic_info) PURE; | ||
| Http::PersistentQuicInfoPtr& quic_info, | ||
| OptRef<Quic::EnvoyQuicNetworkObserverRegistry> network_observer_registry) PURE; | ||
|
|
||
| /** | ||
| * Allocate a TCP connection pool for the host. Pools are separated by 'priority' and | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,6 +77,20 @@ 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), | ||
| quic_upstream_connection_handle_network_change_(Runtime::runtimeFeatureEnabled( | ||
| "envoy.reloadable_features.quic_upstream_connection_handle_network_change")) { | ||
| #ifdef ENVOY_ENABLE_QUIC | ||
| quic_observer_registry_factory_ = | ||
| std::make_unique<Quic::EnvoyMobileQuicNetworkObserverRegistryFactory>(); | ||
| if (quic_upstream_connection_handle_network_change_) { | ||
| cluster_manager_.createNetworkObserverRegistries(*quic_observer_registry_factory_); | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| ConnectivityManagerImpl::NetworkState ConnectivityManagerImpl::network_state_{ | ||
| 1, NetworkType::Generic, MaxFaultThreshold, SocketMode::DefaultPreferredNetworkMode, | ||
| Thread::MutexBasicLockable{}}; | ||
|
|
@@ -102,6 +116,18 @@ 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); | ||
| #ifdef ENVOY_ENABLE_QUIC | ||
| for (std::reference_wrapper<Quic::EnvoyMobileQuicNetworkObserverRegistry> registry : | ||
| quic_observer_registry_factory_->getCreatedObserverRegistries()) { | ||
| registry.get().onNetworkMadeDefault(); | ||
| } | ||
| #endif | ||
| 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()); | ||
|
|
@@ -305,16 +331,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_) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we not doing this when the runtime guard is true?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. Good point. Nice!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,10 @@ | |
| #include "library/common/network/proxy_settings.h" | ||
| #include "library/common/types/c_types.h" | ||
|
|
||
| #ifdef ENVOY_ENABLE_QUIC | ||
| #include "library/common/network/envoy_mobile_quic_network_observer_registry_factory.h" | ||
| #endif | ||
|
|
||
| /** | ||
| * envoy_netconf_t identifies a snapshot of network configuration state. It's returned from calls | ||
| * that may alter current state, and passed back as a parameter to this API to determine if calls | ||
|
|
@@ -186,6 +190,13 @@ class ConnectivityManager | |
| * @returns the default DNS cache set up in base configuration or nullptr. | ||
| */ | ||
| virtual Extensions::Common::DynamicForwardProxy::DnsCacheSharedPtr dnsCache() PURE; | ||
|
|
||
| /** | ||
| * Called when OS changes the preferred network. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: "OS" => "the OS"
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| * @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, | ||
|
|
@@ -201,8 +212,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( | ||
|
|
@@ -232,6 +242,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 { | ||
|
|
@@ -252,9 +263,14 @@ class ConnectivityManagerImpl : public ConnectivityManager, | |
| Extensions::Common::DynamicForwardProxy::DnsCache::AddUpdateCallbacksHandlePtr | ||
| dns_callbacks_handle_{nullptr}; | ||
| Upstream::ClusterManager& cluster_manager_; | ||
| #ifdef ENVOY_ENABLE_QUIC | ||
| std::unique_ptr<Quic::EnvoyMobileQuicNetworkObserverRegistryFactory> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I looks like this is always created (when QUIC is compiled in). If so, consider making this a non-pointer member. That would avoid the need to explicitly construct it in the constructor.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| quic_observer_registry_factory_; | ||
| #endif | ||
| DnsCacheManagerSharedPtr dns_cache_manager_; | ||
| ProxySettingsConstSharedPtr proxy_settings_; | ||
| static NetworkState network_state_; | ||
| const bool quic_upstream_connection_handle_network_change_; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: comment, please.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| }; | ||
|
|
||
| using ConnectivityManagerSharedPtr = std::shared_ptr<ConnectivityManager>; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| #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(Runtime::runtimeFeatureEnabled( | ||
| "envoy.reloadable_features.quic_upstream_connection_handle_network_change")); | ||
| dispatcher_.post([this]() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, why is this posted to the dispatcher instead of running on the current thread? Maybe add a comment?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is called on from Android API, so not necessarily on the network thread.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you clue me in on where it's called unsafely? It looks like it's called from the connection manager's onnetworkmadedefault which is called from InternalEngine::onDefaultNetworkChanged which already switches to the dispatcher context.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also I'm going to pause here because of quic-specific logic and wait for a discussion of general non-quic plans
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I didn't notice that InternalEngine already post()'ed the callback. E-M only has one dispatcher, so probably we can omit post()'ing here. But theoretically the observer registry is one per worker thread (owned by ThreadLocalClusterManagerImpl), and its onNetworkMadeDefault() is called on the main thread even though InternalEngine already switches to the dispatcher context via post(). On the other hand, E-M only has one network thread and dispatcher. I'm wondering if it's clearer to explicitly do post() here to fit the threading model in Envoy core or skip doing post() given this is E-M code.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the post() here and add assert on |
||
| // Retain the existing observers in a list and iterate on the list. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to do this? Does the list get potentially mutated?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, the conn pool may create new connections as a back fill if the connections on this list gets closed. |
||
| std::list<QuicNetworkConnectivityObserver*> existing_observers; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: a vector with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| for (QuicNetworkConnectivityObserver* observer : registeredQuicObservers()) { | ||
| existing_observers.push_back(observer); | ||
| } | ||
| for (auto* observer : existing_observers) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: explicit type here (I think
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| 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,41 @@ | ||
| #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) {} | ||
|
|
||
| void onNetworkMadeDefault(); | ||
|
danzh2010 marked this conversation as resolved.
Outdated
|
||
|
|
||
| private: | ||
| Event::Dispatcher& dispatcher_; | ||
| }; | ||
|
|
||
| class EnvoyMobileQuicNetworkObserverRegistryFactory | ||
| : public EnvoyQuicNetworkObserverRegistryFactory { | ||
| public: | ||
| EnvoyQuicNetworkObserverRegistryPtr | ||
| createQuicNetworkObserverRegistry(Event::Dispatcher& dispatcher) override; | ||
|
|
||
| std::list<std::reference_wrapper<EnvoyMobileQuicNetworkObserverRegistry>>& | ||
| getCreatedObserverRegistries() { | ||
| return thread_local_observer_registries_; | ||
| } | ||
|
|
||
| private: | ||
| std::list<std::reference_wrapper<EnvoyMobileQuicNetworkObserverRegistry>> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto about using a vector instead of a list.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| thread_local_observer_registries_; | ||
| }; | ||
|
|
||
| using EnvoyMobileQuicNetworkObserverRegistryPtr = | ||
| std::unique_ptr<EnvoyMobileQuicNetworkObserverRegistry>; | ||
|
|
||
| } // namespace Quic | ||
| } // namespace Envoy | ||
Uh oh!
There was an error while loading. Please reload this page.