// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/quic/quic_connectivity_monitor.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" namespace net { namespace { bool IsErrorRelatedToConnectivity(int error_code) { return (error_code == ERR_ADDRESS_UNREACHABLE || error_code == ERR_ACCESS_DENIED || error_code == ERR_INTERNET_DISCONNECTED); } } // namespace QuicConnectivityMonitor::QuicConnectivityMonitor( handles::NetworkHandle default_network) : default_network_(default_network) {} QuicConnectivityMonitor::~QuicConnectivityMonitor() = default; void QuicConnectivityMonitor::RecordConnectivityStatsToHistograms( const std::string& notification, handles::NetworkHandle affected_network) const { if (notification == "OnNetworkSoonToDisconnect" || notification == "OnNetworkDisconnected") { // If the disconnected network is not the default network, ignore // stats collections. if (affected_network != default_network_) return; } base::ClampedNumeric num_degrading_sessions = GetNumDegradingSessions(); if (num_sessions_active_during_current_speculative_connectivity_failure_) { UMA_HISTOGRAM_COUNTS_100( "Net.QuicConnectivityMonitor.NumSessionsTrackedSinceSpeculativeError", num_sessions_active_during_current_speculative_connectivity_failure_ .value()); } UMA_HISTOGRAM_COUNTS_100( "Net.QuicConnectivityMonitor.NumActiveQuicSessionsAtNetworkChange", active_sessions_.size()); int percentage = 0; if (num_sessions_active_during_current_speculative_connectivity_failure_ && num_sessions_active_during_current_speculative_connectivity_failure_ .value() > 0) { percentage = base::saturated_cast( num_all_degraded_sessions_ * 100.0 / num_sessions_active_during_current_speculative_connectivity_failure_ .value()); } UMA_HISTOGRAM_COUNTS_100( "Net.QuicConnectivityMonitor.NumAllSessionsDegradedAtNetworkChange", num_all_degraded_sessions_); const std::string raw_histogram_name1 = "Net.QuicConnectivityMonitor.NumAllDegradedSessions." + notification; base::UmaHistogramCustomCounts(raw_histogram_name1, num_all_degraded_sessions_, 1, 100, 50); const std::string percentage_histogram_name1 = "Net.QuicConnectivityMonitor.PercentageAllDegradedSessions." + notification; base::UmaHistogramPercentageObsoleteDoNotUse(percentage_histogram_name1, percentage); // Skip degrading session collection if there are less than two sessions. if (active_sessions_.size() < 2u) return; const std::string raw_histogram_name = "Net.QuicConnectivityMonitor.NumActiveDegradingSessions." + notification; base::UmaHistogramCustomCounts(raw_histogram_name, num_degrading_sessions, 1, 100, 50); percentage = base::saturated_cast(num_degrading_sessions * 100.0 / active_sessions_.size()); const std::string percentage_histogram_name = "Net.QuicConnectivityMonitor.PercentageActiveDegradingSessions." + notification; base::UmaHistogramPercentageObsoleteDoNotUse(percentage_histogram_name, percentage); } size_t QuicConnectivityMonitor::GetNumDegradingSessions() const { return degrading_sessions_.size(); } size_t QuicConnectivityMonitor::GetCountForWriteErrorCode( int write_error_code) const { auto it = write_error_map_.find(write_error_code); return it == write_error_map_.end() ? 0u : it->second; } void QuicConnectivityMonitor::SetInitialDefaultNetwork( handles::NetworkHandle default_network) { default_network_ = default_network; } void QuicConnectivityMonitor::OnSessionPathDegrading( QuicChromiumClientSession* session, handles::NetworkHandle network) { if (network != default_network_) return; degrading_sessions_.insert(session); num_all_degraded_sessions_++; // If the degrading session used to be on the previous default network, it is // possible that the session is no longer tracked in |active_sessions_| due // to the recent default network change. active_sessions_.insert(session); if (!num_sessions_active_during_current_speculative_connectivity_failure_) { num_sessions_active_during_current_speculative_connectivity_failure_ = active_sessions_.size(); } else { // Before seeing session degrading, PACKET_WRITE_ERROR has been observed. UMA_HISTOGRAM_COUNTS_100( "Net.QuicConnectivityMonitor.NumWriteErrorsSeenBeforeDegradation", quic_error_map_[quic::QUIC_PACKET_WRITE_ERROR]); } } void QuicConnectivityMonitor::OnSessionResumedPostPathDegrading( QuicChromiumClientSession* session, handles::NetworkHandle network) { if (network != default_network_) return; degrading_sessions_.erase(session); // If the resumed session used to be on the previous default network, it is // possible that the session is no longer tracked in |active_sessions_| due // to the recent default network change. active_sessions_.insert(session); num_all_degraded_sessions_ = 0u; num_sessions_active_during_current_speculative_connectivity_failure_ = absl::nullopt; } void QuicConnectivityMonitor::OnSessionEncounteringWriteError( QuicChromiumClientSession* session, handles::NetworkHandle network, int error_code) { if (network != default_network_) return; // If the session used to be on the previous default network, it is // possible that the session is no longer tracked in |active_sessions_| due // to the recent default network change. active_sessions_.insert(session); ++write_error_map_[error_code]; bool is_session_degraded = degrading_sessions_.find(session) != degrading_sessions_.end(); UMA_HISTOGRAM_BOOLEAN( "Net.QuicConnectivityMonitor.SessionDegradedBeforeWriteError", is_session_degraded); if (!num_sessions_active_during_current_speculative_connectivity_failure_ && IsErrorRelatedToConnectivity(error_code)) { num_sessions_active_during_current_speculative_connectivity_failure_ = active_sessions_.size(); } } void QuicConnectivityMonitor::OnSessionClosedAfterHandshake( QuicChromiumClientSession* session, handles::NetworkHandle network, quic::ConnectionCloseSource source, quic::QuicErrorCode error_code) { if (network != default_network_) return; if (source == quic::ConnectionCloseSource::FROM_PEER) { // Connection closed by the peer post handshake with PUBLIC RESET // is most likely a NAT rebinding issue. if (error_code == quic::QUIC_PUBLIC_RESET) quic_error_map_[error_code]++; return; } if (error_code == quic::QUIC_PACKET_WRITE_ERROR || error_code == quic::QUIC_TOO_MANY_RTOS) { // Connection close by self with PACKET_WRITE_ERROR or TOO_MANY_RTOS // is likely a connectivity issue. quic_error_map_[error_code]++; } } void QuicConnectivityMonitor::OnSessionRegistered( QuicChromiumClientSession* session, handles::NetworkHandle network) { if (network != default_network_) return; active_sessions_.insert(session); if (num_sessions_active_during_current_speculative_connectivity_failure_) { num_sessions_active_during_current_speculative_connectivity_failure_ .value()++; } } void QuicConnectivityMonitor::OnSessionRemoved( QuicChromiumClientSession* session) { degrading_sessions_.erase(session); active_sessions_.erase(session); } void QuicConnectivityMonitor::OnDefaultNetworkUpdated( handles::NetworkHandle default_network) { default_network_ = default_network; active_sessions_.clear(); degrading_sessions_.clear(); num_sessions_active_during_current_speculative_connectivity_failure_ = absl::nullopt; write_error_map_.clear(); quic_error_map_.clear(); } void QuicConnectivityMonitor::OnIPAddressChanged() { // If handles::NetworkHandle is supported, connectivity monitor will receive // notifications via OnDefaultNetworkUpdated. if (NetworkChangeNotifier::AreNetworkHandlesSupported()) return; DCHECK_EQ(default_network_, handles::kInvalidNetworkHandle); degrading_sessions_.clear(); write_error_map_.clear(); } void QuicConnectivityMonitor::OnSessionGoingAwayOnIPAddressChange( QuicChromiumClientSession* session) { // This should only be called after ConnectivityMonitor gets notified via // OnIPAddressChanged(). DCHECK(degrading_sessions_.empty()); // |session| that encounters IP address change will lose track which network // it is bound to. Future connectivity monitoring may be misleading. session->RemoveConnectivityObserver(this); } } // namespace net