| // Copyright 2018 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SHILL_NETWORK_CONNECTION_DIAGNOSTICS_H_ |
| #define SHILL_NETWORK_CONNECTION_DIAGNOSTICS_H_ |
| |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include <base/cancelable_callback.h> |
| #include <base/functional/callback.h> |
| #include <base/memory/weak_ptr.h> |
| #include <base/time/time.h> |
| #include <base/types/expected.h> |
| #include <net-base/http_url.h> |
| #include <net-base/ip_address.h> |
| |
| #include "shill/metrics.h" |
| #include "shill/mockable.h" |
| |
| namespace shill { |
| |
| class DnsClient; |
| class Error; |
| class EventDispatcher; |
| class IcmpSession; |
| class IcmpSessionFactory; |
| |
| // Given a connected Network and a URL, ConnectionDiagnostics performs the |
| // following actions to diagnose a connectivity problem on the current |
| // Connection: |
| // (A) Starts by pinging all DNS servers. |
| // (B) If none of the DNS servers reply to pings, then we might have a |
| // problem reaching DNS servers. Check if the gateway can be pinged |
| // (step I). |
| // (C) If at least one DNS server replies to pings but we are out of DNS |
| // retries, the DNS servers are at fault. END. |
| // (D) If at least one DNS server replies to pings, and we have DNS |
| // retries left, resolve the IP of the target web server via DNS. |
| // (E) If DNS resolution fails because of a timeout, ping all DNS |
| // servers again and find a new reachable DNS server (step A). |
| // (F) If DNS resolution fails for any other reason, we have found a |
| // DNS server issue. END. |
| // (G) Otherwise, ping the IP address of the target web server. |
| // (H) If ping is successful, we can reach the target web server. We |
| // might have a HTTP issue or a broken portal. END. |
| // (I) If ping is unsuccessful, ping the IP address of the gateway. |
| // (J) If the local gateway respond to pings, then we have |
| // found an upstream connectivity problem or gateway |
| // problem. END. |
| // (K) If there is no response, then the local gateway may not |
| // be responding to pings, or it may not exist on the local |
| // network or be unreachable if there are link layer issues. |
| // END. |
| // |
| // TODO(samueltan): Step F: if retry succeeds, remove the unresponsive DNS |
| // servers so Chrome does not try to use them. |
| class ConnectionDiagnostics { |
| public: |
| // The ConnectionDiagnostics::kEventNames string array depends on this enum. |
| // Any changes to this enum should be synced with that array. |
| enum Type { |
| kTypePingDNSServers = 1, |
| kTypeResolveTargetServerIP = 2, |
| kTypePingTargetServer = 3, |
| kTypePingGateway = 4, |
| }; |
| |
| // The ConnectionDiagnostics::kPhaseNames string array depends on this enum. |
| // Any changes to this enum should be synced with that array. |
| enum Phase { |
| kPhaseStart = 0, |
| kPhaseEnd = 1, |
| }; |
| |
| // The ConnectionDiagnostics::kResultNames string array depends on this enum. |
| // Any changes to this enum should be synced with that array. |
| enum Result { kResultSuccess = 0, kResultFailure = 1, kResultTimeout = 2 }; |
| |
| struct Event { |
| Event(Type type_in, |
| Phase phase_in, |
| Result result_in, |
| const std::string& message_in) |
| : type(type_in), |
| phase(phase_in), |
| result(result_in), |
| message(message_in) {} |
| Type type; |
| Phase phase; |
| Result result; |
| std::string message; |
| }; |
| |
| // The result of the diagnostics is a string describing the connection issue |
| // detected (if any), and list of events (e.g. routing table |
| // lookup, DNS resolution) performed during the diagnostics. |
| using ResultCallback = |
| base::OnceCallback<void(const std::string&, const std::vector<Event>&)>; |
| |
| // TODO(b/229309479) Remove obsolete descriptions. |
| // Metrics::NotifyConnectionDiagnosticsIssue depends on these kIssue strings. |
| // Any changes to these strings should be synced with that Metrics function. |
| static const char kIssueIPCollision[]; |
| static const char kIssueRouting[]; |
| static const char kIssueHTTP[]; |
| static const char kIssueDNSServerMisconfig[]; |
| static const char kIssueDNSServerNoResponse[]; |
| static const char kIssueNoDNSServersConfigured[]; |
| static const char kIssueDNSServersInvalid[]; |
| static const char kIssueNone[]; |
| static const char kIssueGatewayUpstream[]; |
| static const char kIssueGatewayNotResponding[]; |
| static const char kIssueServerNotResponding[]; |
| static const char kIssueGatewayArpFailed[]; |
| static const char kIssueServerArpFailed[]; |
| static const char kIssueInternalError[]; |
| static const char kIssueGatewayNoNeighborEntry[]; |
| static const char kIssueServerNoNeighborEntry[]; |
| static const char kIssueGatewayNeighborEntryNotConnected[]; |
| static const char kIssueServerNeighborEntryNotConnected[]; |
| |
| ConnectionDiagnostics(std::string_view iface_name, |
| int iface_index, |
| const net_base::IPAddress& ip_address, |
| const net_base::IPAddress& gateway, |
| const std::vector<net_base::IPAddress>& dns_list, |
| EventDispatcher* dispatcher, |
| Metrics* metrics, |
| ResultCallback result_callback); |
| ConnectionDiagnostics(const ConnectionDiagnostics&) = delete; |
| ConnectionDiagnostics& operator=(const ConnectionDiagnostics&) = delete; |
| |
| virtual ~ConnectionDiagnostics(); |
| |
| // Performs connectivity diagnostics for the hostname of the URL |url|. |
| mockable bool Start(const net_base::HttpUrl& url); |
| void Stop(); |
| |
| // Returns a string representation of |event|. |
| static std::string EventToString(const Event& event); |
| |
| bool running() const { return running_; } |
| |
| private: |
| friend class ConnectionDiagnosticsTest; |
| |
| static const int kMaxDNSRetries; |
| |
| // Create a new Event with |type|, |phase|, |result|, and an empty message, |
| // and add it to the end of |diagnostic_events_|. |
| void AddEvent(Type type, Phase phase, Result result); |
| |
| // Same as ConnectionDiagnostics::AddEvent, except that the added event |
| // contains the string |message|. |
| void AddEventWithMessage(Type type, |
| Phase phase, |
| Result result, |
| const std::string& message); |
| |
| // Calls |result_callback_|, then stops connection diagnostics. |
| // |diagnostic_events_| and |issue| are passed as arguments to |
| // |result_callback_| to report the results of the diagnostics. |
| void ReportResultAndStop(const std::string& issue); |
| |
| // Attempts to resolve the IP address of the hostname of |target_url_| using |
| // |dns_list|. |
| void ResolveTargetServerIPAddress(const std::vector<std::string>& dns_list); |
| |
| // Pings all the DNS servers of |dns_list_|. |
| void PingDNSServers(); |
| |
| // Starts an IcmpSession with |address|. Called when we want to ping the |
| // target web server or local gateway. |
| void PingHost(const net_base::IPAddress& address); |
| |
| // Called after each IcmpSession started in |
| // ConnectionDiagnostics::PingDNSServers finishes or times out. The DNS server |
| // that was pinged can be uniquely identified with |dns_server_index|. |
| // Attempts to resolve the IP address of the hostname of |target_url_| again |
| // if at least one DNS server was pinged successfully, and if |
| // |num_dns_attempts_| has not yet reached |kMaxDNSRetries|. |
| void OnPingDNSServerComplete(int dns_server_index, |
| const std::vector<base::TimeDelta>& result); |
| |
| // Called after the DNS IP address resolution on started in |
| // ConnectionDiagnostics::ResolveTargetServerIPAddress completes. |
| void OnDNSResolutionComplete( |
| const base::expected<net_base::IPAddress, Error>& address); |
| |
| // Called after the IcmpSession started in ConnectionDiagnostics::PingHost on |
| // |address_pinged| finishes or times out. |ping_event_type| indicates the |
| // type of ping that was started (gateway or target web server), and |result| |
| // is the result of the IcmpSession. |
| void OnPingHostComplete(Type ping_event_type, |
| const net_base::IPAddress& address_pinged, |
| const std::vector<base::TimeDelta>& result); |
| |
| // Utility function that returns true iff the event in |diagnostic_events_| |
| // that is |num_events_ago| before the last event has a matching |type|, |
| // |phase|, and |result|. |
| bool DoesPreviousEventMatch(Type type, |
| Phase phase, |
| Result result, |
| size_t num_events_ago); |
| |
| EventDispatcher* dispatcher_; |
| Metrics* metrics_; |
| |
| // The name of the network interface associated with the connection. |
| std::string iface_name_; |
| // The index of the network interface associated with the connection. |
| int iface_index_; |
| // The IP address of the network interface to use for the diagnostic. |
| net_base::IPAddress ip_address_; |
| // The IP address of the gateway. |
| net_base::IPAddress gateway_; |
| std::vector<net_base::IPAddress> dns_list_; |
| |
| // TODO(b/307880493): Migrate to net_base::DNSClient. |
| std::unique_ptr<DnsClient> dns_client_; |
| std::unique_ptr<IcmpSession> icmp_session_; |
| |
| // The URL whose hostname is being diagnosed. Only defined when the |
| // diagnostics is running. |
| std::optional<net_base::HttpUrl> target_url_; |
| |
| // Used to ping multiple DNS servers in parallel. |
| std::map<int, std::unique_ptr<IcmpSession>> |
| id_to_pending_dns_server_icmp_session_; |
| // TODO(b/307880493): Migrate to net_base::DNSClient and avoid |
| // converting the pingable net_base::IPAddress values to std::string. |
| std::vector<std::string> pingable_dns_servers_; |
| |
| int num_dns_attempts_; |
| bool running_; |
| |
| ResultCallback result_callback_; |
| |
| // Record of all diagnostic events that occurred, sorted in order of |
| // occurrence. |
| std::vector<Event> diagnostic_events_; |
| |
| base::WeakPtrFactory<ConnectionDiagnostics> weak_ptr_factory_; |
| }; |
| |
| // The factory class of the ConnectionDiagnostics, used to derive a mock factory |
| // to create mock ConnectionDiagnostics instance at testing. |
| class ConnectionDiagnosticsFactory { |
| public: |
| ConnectionDiagnosticsFactory() = default; |
| virtual ~ConnectionDiagnosticsFactory() = default; |
| |
| // The default factory method, calling ConnectionDiagnostics's constructor. |
| mockable std::unique_ptr<ConnectionDiagnostics> Create( |
| std::string_view iface_name, |
| int iface_index, |
| const net_base::IPAddress& ip_address, |
| const net_base::IPAddress& gateway, |
| const std::vector<net_base::IPAddress>& dns_list, |
| EventDispatcher* dispatcher, |
| Metrics* metrics, |
| ConnectionDiagnostics::ResultCallback result_callback); |
| }; |
| |
| } // namespace shill |
| |
| #endif // SHILL_NETWORK_CONNECTION_DIAGNOSTICS_H_ |