898 lines
36 KiB
C++
898 lines
36 KiB
C++
//
|
|
// Copyright (C) 2015 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#include "shill/connection_diagnostics.h"
|
|
|
|
#include <base/bind.h>
|
|
#include <base/strings/stringprintf.h>
|
|
|
|
#include "shill/arp_client.h"
|
|
#include "shill/arp_packet.h"
|
|
#include "shill/connection.h"
|
|
#include "shill/connectivity_trial.h"
|
|
#include "shill/device_info.h"
|
|
#include "shill/dns_client.h"
|
|
#include "shill/dns_client_factory.h"
|
|
#include "shill/error.h"
|
|
#include "shill/event_dispatcher.h"
|
|
#include "shill/http_url.h"
|
|
#include "shill/icmp_session.h"
|
|
#include "shill/icmp_session_factory.h"
|
|
#include "shill/logging.h"
|
|
#include "shill/metrics.h"
|
|
#include "shill/net/byte_string.h"
|
|
#include "shill/net/rtnl_handler.h"
|
|
#include "shill/net/rtnl_listener.h"
|
|
#include "shill/net/rtnl_message.h"
|
|
#include "shill/routing_table.h"
|
|
#include "shill/routing_table_entry.h"
|
|
|
|
using base::Bind;
|
|
using base::StringPrintf;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace {
|
|
// These strings are dependent on ConnectionDiagnostics::Type. Any changes to
|
|
// this array should be synced with ConnectionDiagnostics::Type.
|
|
const char* kEventNames[] = {
|
|
"Portal detection",
|
|
"Ping DNS servers",
|
|
"DNS resolution",
|
|
"Ping (target web server)",
|
|
"Ping (gateway)",
|
|
"Find route",
|
|
"ARP table lookup",
|
|
"Neighbor table lookup",
|
|
"IP collision check"
|
|
};
|
|
// These strings are dependent on ConnectionDiagnostics::Phase. Any changes to
|
|
// this array should be synced with ConnectionDiagnostics::Phase.
|
|
const char* kPhaseNames[] = {
|
|
"Start",
|
|
"End",
|
|
"End (Content)",
|
|
"End (DNS)",
|
|
"End (HTTP/CXN)"
|
|
};
|
|
// These strings are dependent on ConnectionDiagnostics::Result. Any changes to
|
|
// this array should be synced with ConnectionDiagnostics::Result.
|
|
const char* kResultNames[] = {
|
|
"Success",
|
|
"Failure",
|
|
"Timeout"
|
|
};
|
|
// After we fail to ping the gateway, we 1) start ARP lookup, 2) fail ARP
|
|
// lookup, 3) start IP collision check, 4) end IP collision check.
|
|
const int kNumEventsFromPingGatewayEndToIpCollisionCheckEnd = 4;
|
|
} // namespace
|
|
|
|
namespace shill {
|
|
|
|
namespace Logging {
|
|
static auto kModuleLogScope = ScopeLogger::kWiFi;
|
|
static string ObjectID(ConnectionDiagnostics* n) {
|
|
return "(connection_diagnostics)";
|
|
}
|
|
}
|
|
|
|
const char ConnectionDiagnostics::kIssueIPCollision[] =
|
|
"IP collision detected. Another host on the local network has been "
|
|
"assigned the same IP address.";
|
|
const char ConnectionDiagnostics::kIssueRouting[] = "Routing problem detected.";
|
|
const char ConnectionDiagnostics::kIssueHTTPBrokenPortal[] =
|
|
"Target URL is pingable. Connectivity problems might be caused by HTTP "
|
|
"issues on the server or a broken portal.";
|
|
const char ConnectionDiagnostics::kIssueDNSServerMisconfig[] =
|
|
"DNS servers responding to DNS queries, but sending invalid responses. "
|
|
"DNS servers might be misconfigured.";
|
|
const char ConnectionDiagnostics::kIssueDNSServerNoResponse[] =
|
|
"At least one DNS server is pingable, but is not responding to DNS "
|
|
"requests. DNS server issue detected.";
|
|
const char ConnectionDiagnostics::kIssueNoDNSServersConfigured[] =
|
|
"No DNS servers have been configured for this connection -- either the "
|
|
"DHCP server or user configuration is invalid.";
|
|
const char ConnectionDiagnostics::kIssueDNSServersInvalid[] =
|
|
"All configured DNS server addresses are invalid.";
|
|
const char ConnectionDiagnostics::kIssueNone[] =
|
|
"No connection issue detected.";
|
|
const char ConnectionDiagnostics::kIssueCaptivePortal[] =
|
|
"Trapped in captive portal.";
|
|
const char ConnectionDiagnostics::kIssueGatewayUpstream[] =
|
|
"We can find a route to the target web server at a remote IP address, "
|
|
"and the local gateway is pingable. Gatway issue or upstream "
|
|
"connectivity problem detected.";
|
|
const char ConnectionDiagnostics::kIssueGatewayNotResponding[] =
|
|
"This gateway appears to be on the local network, but is not responding to "
|
|
"pings.";
|
|
const char ConnectionDiagnostics::kIssueServerNotResponding[] =
|
|
"This web server appears to be on the local network, but is not responding "
|
|
"to pings.";
|
|
const char ConnectionDiagnostics::kIssueGatewayArpFailed[] =
|
|
"No ARP entry for the gateway. Either the gateway does not exist on the "
|
|
"local network, or there are link layer issues.";
|
|
const char ConnectionDiagnostics::kIssueServerArpFailed[] =
|
|
"No ARP entry for the web server. Either the web server does not exist on "
|
|
"the local network, or there are link layer issues.";
|
|
const char ConnectionDiagnostics::kIssueInternalError[] =
|
|
"The connection diagnostics encountered an internal failure.";
|
|
const char ConnectionDiagnostics::kIssueGatewayNoNeighborEntry[] =
|
|
"No neighbor table entry for the gateway. Either the gateway does not "
|
|
"exist on the local network, or there are link layer issues.";
|
|
const char ConnectionDiagnostics::kIssueServerNoNeighborEntry[] =
|
|
"No neighbor table entry for the web server. Either the web server does "
|
|
"not exist on the local network, or there are link layer issues.";
|
|
const char ConnectionDiagnostics::kIssueGatewayNeighborEntryNotConnected[] =
|
|
"Neighbor table entry for the gateway is not in a connected state. Either "
|
|
"the web server does not exist on the local network, or there are link "
|
|
"layer issues.";
|
|
const char ConnectionDiagnostics::kIssueServerNeighborEntryNotConnected[] =
|
|
"Neighbor table entry for the web server is not in a connected state. "
|
|
"Either the web server does not exist on the local network, or there are "
|
|
"link layer issues.";
|
|
const int ConnectionDiagnostics::kMaxDNSRetries = 2;
|
|
const int ConnectionDiagnostics::kRouteQueryTimeoutSeconds = 1;
|
|
const int ConnectionDiagnostics::kArpReplyTimeoutSeconds = 1;
|
|
const int ConnectionDiagnostics::kNeighborTableRequestTimeoutSeconds = 1;
|
|
const int ConnectionDiagnostics::kDNSTimeoutSeconds = 3;
|
|
|
|
ConnectionDiagnostics::ConnectionDiagnostics(
|
|
ConnectionRefPtr connection, EventDispatcher* dispatcher, Metrics* metrics,
|
|
const DeviceInfo* device_info, const ResultCallback& result_callback)
|
|
: weak_ptr_factory_(this),
|
|
dispatcher_(dispatcher),
|
|
metrics_(metrics),
|
|
routing_table_(RoutingTable::GetInstance()),
|
|
rtnl_handler_(RTNLHandler::GetInstance()),
|
|
connection_(connection),
|
|
device_info_(device_info),
|
|
dns_client_factory_(DNSClientFactory::GetInstance()),
|
|
portal_detector_(new PortalDetector(
|
|
connection_, dispatcher_,
|
|
Bind(&ConnectionDiagnostics::StartAfterPortalDetectionInternal,
|
|
weak_ptr_factory_.GetWeakPtr()))),
|
|
arp_client_(new ArpClient(connection_->interface_index())),
|
|
icmp_session_(new IcmpSession(dispatcher_)),
|
|
icmp_session_factory_(IcmpSessionFactory::GetInstance()),
|
|
num_dns_attempts_(0),
|
|
running_(false),
|
|
result_callback_(result_callback) {}
|
|
|
|
ConnectionDiagnostics::~ConnectionDiagnostics() {
|
|
Stop();
|
|
}
|
|
|
|
bool ConnectionDiagnostics::Start(const string& url_string) {
|
|
SLOG(this, 3) << __func__ << "(" << url_string << ")";
|
|
|
|
if (running()) {
|
|
LOG(ERROR) << "Connection diagnostics already started";
|
|
return false;
|
|
}
|
|
|
|
target_url_.reset(new HTTPURL());
|
|
if (!target_url_->ParseFromString(url_string)) {
|
|
LOG(ERROR) << "Failed to parse URL string: " << url_string;
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
if (!portal_detector_->Start(url_string)) {
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
running_ = true;
|
|
AddEvent(kTypePortalDetection, kPhaseStart, kResultSuccess);
|
|
return true;
|
|
}
|
|
|
|
bool ConnectionDiagnostics::StartAfterPortalDetection(
|
|
const string& url_string, const PortalDetector::Result& result) {
|
|
SLOG(this, 3) << __func__ << "(" << url_string << ")";
|
|
|
|
if (running()) {
|
|
LOG(ERROR) << "Connection diagnostics already started";
|
|
return false;
|
|
}
|
|
|
|
target_url_.reset(new HTTPURL());
|
|
if (!target_url_->ParseFromString(url_string)) {
|
|
LOG(ERROR) << "Failed to parse URL string: " << url_string;
|
|
Stop();
|
|
return false;
|
|
}
|
|
|
|
running_ = true;
|
|
dispatcher_->PostTask(
|
|
Bind(&ConnectionDiagnostics::StartAfterPortalDetectionInternal,
|
|
weak_ptr_factory_.GetWeakPtr(), result));
|
|
return true;
|
|
}
|
|
|
|
void ConnectionDiagnostics::Stop() {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
running_ = false;
|
|
num_dns_attempts_ = 0;
|
|
diagnostic_events_.clear();
|
|
dns_client_.reset();
|
|
arp_client_->Stop();
|
|
icmp_session_->Stop();
|
|
portal_detector_.reset();
|
|
receive_response_handler_.reset();
|
|
neighbor_msg_listener_.reset();
|
|
id_to_pending_dns_server_icmp_session_.clear();
|
|
target_url_.reset();
|
|
route_query_callback_.Cancel();
|
|
route_query_timeout_callback_.Cancel();
|
|
arp_reply_timeout_callback_.Cancel();
|
|
neighbor_request_timeout_callback_.Cancel();
|
|
}
|
|
|
|
// static
|
|
string ConnectionDiagnostics::EventToString(const Event& event) {
|
|
string message("");
|
|
message.append(StringPrintf("Event: %-26sPhase: %-17sResult: %-10s",
|
|
kEventNames[event.type], kPhaseNames[event.phase],
|
|
kResultNames[event.result]));
|
|
if (!event.message.empty()) {
|
|
message.append(StringPrintf("Msg: %s", event.message.c_str()));
|
|
}
|
|
return message;
|
|
}
|
|
|
|
void ConnectionDiagnostics::AddEvent(Type type, Phase phase, Result result) {
|
|
AddEventWithMessage(type, phase, result, "");
|
|
}
|
|
|
|
void ConnectionDiagnostics::AddEventWithMessage(Type type, Phase phase,
|
|
Result result,
|
|
const string& message) {
|
|
diagnostic_events_.push_back(Event(type, phase, result, message));
|
|
}
|
|
|
|
void ConnectionDiagnostics::ReportResultAndStop(const string& issue) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
metrics_->NotifyConnectionDiagnosticsIssue(issue);
|
|
if (!result_callback_.is_null()) {
|
|
LOG(INFO) << "Connection diagnostics events:";
|
|
for (size_t i = 0; i < diagnostic_events_.size(); ++i) {
|
|
LOG(INFO) << " #" << i << ": "
|
|
<< EventToString(diagnostic_events_[i]);
|
|
}
|
|
LOG(INFO) << "Connection diagnostics completed. Connection issue: "
|
|
<< issue;
|
|
result_callback_.Run(issue, diagnostic_events_);
|
|
}
|
|
Stop();
|
|
}
|
|
|
|
void ConnectionDiagnostics::StartAfterPortalDetectionInternal(
|
|
const PortalDetector::Result& result) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
Result result_type;
|
|
if (result.trial_result.status == ConnectivityTrial::kStatusSuccess) {
|
|
result_type = kResultSuccess;
|
|
} else if (result.trial_result.status == ConnectivityTrial::kStatusTimeout) {
|
|
result_type = kResultTimeout;
|
|
} else {
|
|
result_type = kResultFailure;
|
|
}
|
|
|
|
switch (result.trial_result.phase) {
|
|
case ConnectivityTrial::kPhaseContent: {
|
|
AddEvent(kTypePortalDetection, kPhasePortalDetectionEndContent,
|
|
result_type);
|
|
// We have found the issue if we end in the content phase.
|
|
ReportResultAndStop(result_type == kResultSuccess ? kIssueNone
|
|
: kIssueCaptivePortal);
|
|
break;
|
|
}
|
|
case ConnectivityTrial::kPhaseDNS: {
|
|
AddEvent(kTypePortalDetection, kPhasePortalDetectionEndDNS, result_type);
|
|
if (result.trial_result.status == ConnectivityTrial::kStatusSuccess) {
|
|
LOG(ERROR) << __func__ << ": portal detection should not end with "
|
|
"success status in DNS phase";
|
|
ReportResultAndStop(kIssueInternalError);
|
|
} else if (result.trial_result.status ==
|
|
ConnectivityTrial::kStatusTimeout) {
|
|
// DNS timeout occurred in portal detection. Ping DNS servers to make
|
|
// sure they are reachable.
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::PingDNSServers,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
} else {
|
|
ReportResultAndStop(kIssueDNSServerMisconfig);
|
|
}
|
|
break;
|
|
}
|
|
case ConnectivityTrial::kPhaseConnection:
|
|
case ConnectivityTrial::kPhaseHTTP:
|
|
case ConnectivityTrial::kPhaseUnknown:
|
|
default: {
|
|
AddEvent(kTypePortalDetection, kPhasePortalDetectionEndOther,
|
|
result_type);
|
|
if (result.trial_result.status == ConnectivityTrial::kStatusSuccess) {
|
|
LOG(ERROR) << __func__
|
|
<< ": portal detection should not end with success status in"
|
|
" Connection/HTTP/Unknown phase";
|
|
ReportResultAndStop(kIssueInternalError);
|
|
} else {
|
|
dispatcher_->PostTask(
|
|
Bind(&ConnectionDiagnostics::ResolveTargetServerIPAddress,
|
|
weak_ptr_factory_.GetWeakPtr(), connection_->dns_servers()));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::ResolveTargetServerIPAddress(
|
|
const vector<string>& dns_servers) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
Error e;
|
|
dns_client_.reset(dns_client_factory_->CreateDNSClient(
|
|
connection_->IsIPv6() ? IPAddress::kFamilyIPv6 : IPAddress::kFamilyIPv4,
|
|
connection_->interface_name(), dns_servers, kDNSTimeoutSeconds * 1000,
|
|
dispatcher_, Bind(&ConnectionDiagnostics::OnDNSResolutionComplete,
|
|
weak_ptr_factory_.GetWeakPtr())));
|
|
if (!dns_client_->Start(target_url_->host(), &e)) {
|
|
LOG(ERROR) << __func__ << ": could not start DNS -- " << e.message();
|
|
AddEventWithMessage(kTypeResolveTargetServerIP, kPhaseStart, kResultFailure,
|
|
e.message().c_str());
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
AddEventWithMessage(kTypeResolveTargetServerIP, kPhaseStart, kResultSuccess,
|
|
StringPrintf("Attempt #%d", num_dns_attempts_));
|
|
SLOG(this, 3) << __func__ << ": looking up " << target_url_->host()
|
|
<< " (attempt " << num_dns_attempts_ << ")";
|
|
++num_dns_attempts_;
|
|
}
|
|
|
|
void ConnectionDiagnostics::PingDNSServers() {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
if (connection_->dns_servers().empty()) {
|
|
LOG(ERROR) << __func__ << ": no DNS servers for this connection";
|
|
AddEventWithMessage(kTypePingDNSServers, kPhaseStart, kResultFailure,
|
|
"No DNS servers for this connection");
|
|
ReportResultAndStop(kIssueNoDNSServersConfigured);
|
|
return;
|
|
}
|
|
|
|
id_to_pending_dns_server_icmp_session_.clear();
|
|
pingable_dns_servers_.clear();
|
|
size_t num_invalid_dns_server_addr = 0;
|
|
size_t num_failed_icmp_session_start = 0;
|
|
for (size_t i = 0; i < connection_->dns_servers().size(); ++i) {
|
|
// If we encounter any errors starting ping for any DNS server, carry on
|
|
// attempting to ping the other DNS servers rather than failing. We only
|
|
// need to successfully ping a single DNS server to decide whether or not
|
|
// DNS servers can be reached.
|
|
IPAddress dns_server_ip_addr(connection_->dns_servers()[i]);
|
|
if (dns_server_ip_addr.family() == IPAddress::kFamilyUnknown) {
|
|
LOG(ERROR) << __func__
|
|
<< ": could not parse DNS server IP address from string";
|
|
++num_invalid_dns_server_addr;
|
|
continue;
|
|
}
|
|
|
|
bool emplace_success =
|
|
(id_to_pending_dns_server_icmp_session_.emplace(
|
|
i, std::unique_ptr<IcmpSession>(
|
|
icmp_session_factory_->CreateIcmpSession(dispatcher_))))
|
|
.second;
|
|
if (emplace_success &&
|
|
id_to_pending_dns_server_icmp_session_.at(i)
|
|
->Start(dns_server_ip_addr,
|
|
Bind(&ConnectionDiagnostics::OnPingDNSServerComplete,
|
|
weak_ptr_factory_.GetWeakPtr(), i))) {
|
|
SLOG(this, 3) << __func__ << ": pinging DNS server at "
|
|
<< dns_server_ip_addr.ToString();
|
|
} else {
|
|
LOG(ERROR) << "Failed to initiate ping for DNS server at "
|
|
<< dns_server_ip_addr.ToString();
|
|
++num_failed_icmp_session_start;
|
|
if (emplace_success) {
|
|
id_to_pending_dns_server_icmp_session_.erase(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (id_to_pending_dns_server_icmp_session_.empty()) {
|
|
AddEventWithMessage(
|
|
kTypePingDNSServers, kPhaseStart, kResultFailure,
|
|
"Could not start ping for any of the given DNS servers");
|
|
if (num_invalid_dns_server_addr == connection_->dns_servers().size()) {
|
|
ReportResultAndStop(kIssueDNSServersInvalid);
|
|
} else if (num_failed_icmp_session_start ==
|
|
connection_->dns_servers().size()) {
|
|
ReportResultAndStop(kIssueInternalError);
|
|
}
|
|
} else {
|
|
AddEvent(kTypePingDNSServers, kPhaseStart, kResultSuccess);
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::FindRouteToHost(const IPAddress& address) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
RoutingTableEntry entry;
|
|
route_query_callback_.Reset(Bind(&ConnectionDiagnostics::OnRouteQueryResponse,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
if (!routing_table_->RequestRouteToHost(
|
|
address, connection_->interface_index(), -1,
|
|
route_query_callback_.callback(), connection_->table_id())) {
|
|
route_query_callback_.Cancel();
|
|
LOG(ERROR) << __func__ << ": could not request route to "
|
|
<< address.ToString();
|
|
AddEventWithMessage(kTypeFindRoute, kPhaseStart, kResultFailure,
|
|
StringPrintf("Could not request route to %s",
|
|
address.ToString().c_str()));
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
// RoutingTable implementation does not have a built-in timeout mechanism
|
|
// for un-replied route requests, so use our own.
|
|
route_query_timeout_callback_.Reset(
|
|
Bind(&ConnectionDiagnostics::OnRouteQueryTimeout,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
dispatcher_->PostDelayedTask(route_query_timeout_callback_.callback(),
|
|
kRouteQueryTimeoutSeconds * 1000);
|
|
AddEventWithMessage(
|
|
kTypeFindRoute, kPhaseStart, kResultSuccess,
|
|
StringPrintf("Requesting route to %s", address.ToString().c_str()));
|
|
}
|
|
|
|
void ConnectionDiagnostics::FindArpTableEntry(const IPAddress& address) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
if (address.family() != IPAddress::kFamilyIPv4) {
|
|
// We only perform ARP table lookups for IPv4 addresses.
|
|
LOG(ERROR) << __func__ << ": " << address.ToString()
|
|
<< " is not an IPv4 address";
|
|
AddEventWithMessage(
|
|
kTypeArpTableLookup, kPhaseStart, kResultFailure,
|
|
StringPrintf("%s is not an IPv4 address", address.ToString().c_str()));
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
AddEventWithMessage(kTypeArpTableLookup, kPhaseStart, kResultSuccess,
|
|
StringPrintf("Finding ARP table entry for %s",
|
|
address.ToString().c_str()));
|
|
ByteString target_mac_address;
|
|
if (device_info_->GetMACAddressOfPeer(connection_->interface_index(), address,
|
|
&target_mac_address)) {
|
|
AddEventWithMessage(kTypeArpTableLookup, kPhaseEnd, kResultSuccess,
|
|
StringPrintf("Found ARP table entry for %s",
|
|
address.ToString().c_str()));
|
|
ReportResultAndStop(address.Equals(connection_->gateway())
|
|
? kIssueGatewayNotResponding
|
|
: kIssueServerNotResponding);
|
|
return;
|
|
}
|
|
|
|
AddEventWithMessage(kTypeArpTableLookup, kPhaseEnd, kResultFailure,
|
|
StringPrintf("Could not find ARP table entry for %s",
|
|
address.ToString().c_str()));
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::CheckIpCollision,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
void ConnectionDiagnostics::FindNeighborTableEntry(const IPAddress& address) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
if (address.family() != IPAddress::kFamilyIPv6) {
|
|
// We only perform neighbor table lookups for IPv6 addresses.
|
|
LOG(ERROR) << __func__ << ": " << address.ToString()
|
|
<< " is not an IPv6 address";
|
|
AddEventWithMessage(
|
|
kTypeNeighborTableLookup, kPhaseStart, kResultFailure,
|
|
StringPrintf("%s is not an IPv6 address", address.ToString().c_str()));
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
neighbor_msg_listener_.reset(
|
|
new RTNLListener(RTNLHandler::kRequestNeighbor,
|
|
Bind(&ConnectionDiagnostics::OnNeighborMsgReceived,
|
|
weak_ptr_factory_.GetWeakPtr(), address)));
|
|
rtnl_handler_->RequestDump(RTNLHandler::kRequestNeighbor);
|
|
|
|
neighbor_request_timeout_callback_.Reset(
|
|
Bind(&ConnectionDiagnostics::OnNeighborTableRequestTimeout,
|
|
weak_ptr_factory_.GetWeakPtr(), address));
|
|
dispatcher_->PostDelayedTask(route_query_timeout_callback_.callback(),
|
|
kNeighborTableRequestTimeoutSeconds * 1000);
|
|
AddEventWithMessage(kTypeNeighborTableLookup, kPhaseStart, kResultSuccess,
|
|
StringPrintf("Finding neighbor table entry for %s",
|
|
address.ToString().c_str()));
|
|
}
|
|
|
|
void ConnectionDiagnostics::CheckIpCollision() {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
if (!device_info_->GetMACAddress(connection_->interface_index(),
|
|
&local_mac_address_)) {
|
|
LOG(ERROR) << __func__ << ": could not get local MAC address";
|
|
AddEventWithMessage(kTypeIPCollisionCheck, kPhaseStart, kResultFailure,
|
|
"Could not get local MAC address");
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
if (!arp_client_->StartReplyListener()) {
|
|
LOG(ERROR) << __func__ << ": failed to start ARP client";
|
|
AddEventWithMessage(kTypeIPCollisionCheck, kPhaseStart, kResultFailure,
|
|
"Failed to start ARP client");
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
receive_response_handler_.reset(dispatcher_->CreateReadyHandler(
|
|
arp_client_->socket(), IOHandler::kModeInput,
|
|
Bind(&ConnectionDiagnostics::OnArpReplyReceived,
|
|
weak_ptr_factory_.GetWeakPtr())));
|
|
|
|
ArpPacket request(connection_->local(), connection_->local(),
|
|
local_mac_address_, ByteString());
|
|
if (!arp_client_->TransmitRequest(request)) {
|
|
LOG(ERROR) << __func__ << ": failed to send ARP request";
|
|
AddEventWithMessage(kTypeIPCollisionCheck, kPhaseStart, kResultFailure,
|
|
"Failed to send ARP request");
|
|
arp_client_->Stop();
|
|
receive_response_handler_.reset();
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
arp_reply_timeout_callback_.Reset(
|
|
Bind(&ConnectionDiagnostics::OnArpRequestTimeout,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
dispatcher_->PostDelayedTask(arp_reply_timeout_callback_.callback(),
|
|
kArpReplyTimeoutSeconds * 1000);
|
|
AddEvent(kTypeIPCollisionCheck, kPhaseStart, kResultSuccess);
|
|
}
|
|
|
|
void ConnectionDiagnostics::PingHost(const IPAddress& address) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
Type event_type = address.Equals(connection_->gateway())
|
|
? kTypePingGateway
|
|
: kTypePingTargetServer;
|
|
if (!icmp_session_->Start(
|
|
address, Bind(&ConnectionDiagnostics::OnPingHostComplete,
|
|
weak_ptr_factory_.GetWeakPtr(), event_type, address))) {
|
|
LOG(ERROR) << __func__ << ": failed to start ICMP session with "
|
|
<< address.ToString();
|
|
AddEventWithMessage(event_type, kPhaseStart, kResultFailure,
|
|
StringPrintf("Failed to start ICMP session with %s",
|
|
address.ToString().c_str()));
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
AddEventWithMessage(event_type, kPhaseStart, kResultSuccess,
|
|
StringPrintf("Pinging %s", address.ToString().c_str()));
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnPingDNSServerComplete(
|
|
int dns_server_index, const vector<base::TimeDelta>& result) {
|
|
SLOG(this, 3) << __func__ << "(DNS server index " << dns_server_index << ")";
|
|
|
|
if (!id_to_pending_dns_server_icmp_session_.erase(dns_server_index)) {
|
|
// This should not happen, since we expect exactly one callback for each
|
|
// IcmpSession started with a unique |dns_server_index| value in
|
|
// ConnectionDiagnostics::PingDNSServers. However, if this does happen for
|
|
// any reason, |id_to_pending_dns_server_icmp_session_| might never become
|
|
// empty, and we might never move to the next step after pinging DNS
|
|
// servers. Stop diagnostics immediately to prevent this from happening.
|
|
LOG(ERROR) << __func__
|
|
<< ": no matching pending DNS server ICMP session found";
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
|
|
if (IcmpSession::AnyRepliesReceived(result)) {
|
|
pingable_dns_servers_.push_back(
|
|
connection_->dns_servers()[dns_server_index]);
|
|
}
|
|
if (!id_to_pending_dns_server_icmp_session_.empty()) {
|
|
SLOG(this, 3) << __func__ << ": not yet finished pinging all DNS servers";
|
|
return;
|
|
}
|
|
|
|
if (pingable_dns_servers_.empty()) {
|
|
// Use the first DNS server on the list and diagnose its connectivity.
|
|
IPAddress first_dns_server_ip_addr(connection_->dns_servers()[0]);
|
|
if (first_dns_server_ip_addr.family() == IPAddress::kFamilyUnknown) {
|
|
LOG(ERROR) << __func__ << ": could not parse DNS server IP address "
|
|
<< connection_->dns_servers()[0];
|
|
AddEventWithMessage(kTypePingDNSServers, kPhaseEnd, kResultFailure,
|
|
StringPrintf("Could not parse DNS "
|
|
"server IP address %s",
|
|
connection_->dns_servers()[0].c_str()));
|
|
ReportResultAndStop(kIssueInternalError);
|
|
return;
|
|
}
|
|
AddEventWithMessage(
|
|
kTypePingDNSServers, kPhaseEnd, kResultFailure,
|
|
StringPrintf(
|
|
"No DNS servers responded to pings. Pinging first DNS server at %s",
|
|
first_dns_server_ip_addr.ToString().c_str()));
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::FindRouteToHost,
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
first_dns_server_ip_addr));
|
|
return;
|
|
}
|
|
|
|
if (pingable_dns_servers_.size() != connection_->dns_servers().size()) {
|
|
AddEventWithMessage(kTypePingDNSServers, kPhaseEnd, kResultSuccess,
|
|
"Pinged some, but not all, DNS servers successfully");
|
|
} else {
|
|
AddEventWithMessage(kTypePingDNSServers, kPhaseEnd, kResultSuccess,
|
|
"Pinged all DNS servers successfully");
|
|
}
|
|
|
|
if (num_dns_attempts_ < kMaxDNSRetries) {
|
|
dispatcher_->PostTask(
|
|
Bind(&ConnectionDiagnostics::ResolveTargetServerIPAddress,
|
|
weak_ptr_factory_.GetWeakPtr(), pingable_dns_servers_));
|
|
} else {
|
|
SLOG(this, 3) << __func__ << ": max DNS resolution attempts reached";
|
|
ReportResultAndStop(kIssueDNSServerNoResponse);
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnDNSResolutionComplete(const Error& error,
|
|
const IPAddress& address) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
if (error.IsSuccess()) {
|
|
AddEventWithMessage(
|
|
kTypeResolveTargetServerIP, kPhaseEnd, kResultSuccess,
|
|
StringPrintf("Target address is %s", address.ToString().c_str()));
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::PingHost,
|
|
weak_ptr_factory_.GetWeakPtr(), address));
|
|
} else if (error.type() == Error::kOperationTimeout) {
|
|
AddEventWithMessage(
|
|
kTypeResolveTargetServerIP, kPhaseEnd, kResultTimeout,
|
|
StringPrintf("DNS resolution timed out: %s", error.message().c_str()));
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::PingDNSServers,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
} else {
|
|
AddEventWithMessage(
|
|
kTypeResolveTargetServerIP, kPhaseEnd, kResultFailure,
|
|
StringPrintf("DNS resolution failed: %s", error.message().c_str()));
|
|
ReportResultAndStop(kIssueDNSServerMisconfig);
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnPingHostComplete(
|
|
Type ping_event_type, const IPAddress& address_pinged,
|
|
const vector<base::TimeDelta>& result) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
string message(StringPrintf("Destination: %s, Latencies: ",
|
|
address_pinged.ToString().c_str()));
|
|
for (const auto& latency : result) {
|
|
if (latency.is_zero()) {
|
|
message.append("NA ");
|
|
} else {
|
|
message.append(StringPrintf("%4.2fms ", latency.InMillisecondsF()));
|
|
}
|
|
}
|
|
|
|
Result result_type =
|
|
IcmpSession::AnyRepliesReceived(result) ? kResultSuccess : kResultFailure;
|
|
if (IcmpSession::IsPacketLossPercentageGreaterThan(result, 50)) {
|
|
LOG(WARNING) << __func__ << ": high packet loss when pinging "
|
|
<< address_pinged.ToString();
|
|
}
|
|
AddEventWithMessage(ping_event_type, kPhaseEnd, result_type, message);
|
|
if (result_type == kResultSuccess) {
|
|
// If pinging the target web server succeeded, we have found a HTTP issue or
|
|
// broken portal. Otherwise, if pinging the gateway succeeded, we have found
|
|
// an upstream connectivity problem or gateway issue.
|
|
ReportResultAndStop(ping_event_type == kTypePingGateway
|
|
? kIssueGatewayUpstream
|
|
: kIssueHTTPBrokenPortal);
|
|
} else if (result_type == kResultFailure &&
|
|
ping_event_type == kTypePingTargetServer) {
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::FindRouteToHost,
|
|
weak_ptr_factory_.GetWeakPtr(), address_pinged));
|
|
} else if (result_type == kResultFailure &&
|
|
ping_event_type == kTypePingGateway &&
|
|
address_pinged.family() == IPAddress::kFamilyIPv4) {
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::FindArpTableEntry,
|
|
weak_ptr_factory_.GetWeakPtr(), address_pinged));
|
|
} else {
|
|
// We failed to ping an IPv6 gateway. Check for neighbor table entry for
|
|
// this gateway.
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::FindNeighborTableEntry,
|
|
weak_ptr_factory_.GetWeakPtr(), address_pinged));
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnArpReplyReceived(int fd) {
|
|
SLOG(this, 3) << __func__ << "(fd " << fd << ")";
|
|
|
|
ArpPacket packet;
|
|
ByteString sender;
|
|
if (!arp_client_->ReceivePacket(&packet, &sender)) {
|
|
return;
|
|
}
|
|
|
|
if (!packet.IsReply()) {
|
|
SLOG(this, 4) << __func__ << ": this is not a reply packet. Ignoring.";
|
|
return;
|
|
}
|
|
|
|
if (!connection_->local().address().Equals(
|
|
packet.remote_ip_address().address())) {
|
|
SLOG(this, 4) << __func__ << ": response is not for our IP address.";
|
|
return;
|
|
}
|
|
|
|
if (!local_mac_address_.Equals(packet.remote_mac_address())) {
|
|
SLOG(this, 4) << __func__ << ": response is not for our MAC address.";
|
|
return;
|
|
}
|
|
|
|
if (connection_->local().address().Equals(
|
|
packet.local_ip_address().address())) {
|
|
arp_reply_timeout_callback_.Cancel();
|
|
AddEventWithMessage(kTypeIPCollisionCheck, kPhaseEnd, kResultSuccess,
|
|
"IP collision found");
|
|
ReportResultAndStop(kIssueIPCollision);
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnArpRequestTimeout() {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
AddEventWithMessage(kTypeIPCollisionCheck, kPhaseEnd, kResultFailure,
|
|
"No IP collision found");
|
|
// TODO(samueltan): perform link-level diagnostics.
|
|
if (DoesPreviousEventMatch(
|
|
kTypePingGateway, kPhaseEnd, kResultFailure,
|
|
kNumEventsFromPingGatewayEndToIpCollisionCheckEnd)) {
|
|
// We came here from failing to ping the gateway.
|
|
ReportResultAndStop(kIssueGatewayArpFailed);
|
|
} else {
|
|
// Otherwise, we must have come here from failing to ping the target web
|
|
// server and successfully finding a route.
|
|
ReportResultAndStop(kIssueServerArpFailed);
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnNeighborMsgReceived(
|
|
const IPAddress& address_queried, const RTNLMessage& msg) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
DCHECK(msg.type() == RTNLMessage::kTypeNeighbor);
|
|
const RTNLMessage::NeighborStatus& neighbor = msg.neighbor_status();
|
|
|
|
if (neighbor.type != NDA_DST || !msg.HasAttribute(NDA_DST)) {
|
|
SLOG(this, 4) << __func__ << ": neighbor message has no destination";
|
|
return;
|
|
}
|
|
|
|
IPAddress address(msg.family(), msg.GetAttribute(NDA_DST));
|
|
if (!address.Equals(address_queried)) {
|
|
SLOG(this, 4) << __func__ << ": destination address (" << address.ToString()
|
|
<< ") does not match address queried ("
|
|
<< address_queried.ToString() << ")";
|
|
return;
|
|
}
|
|
|
|
neighbor_request_timeout_callback_.Cancel();
|
|
if (!(neighbor.state & (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE))) {
|
|
AddEventWithMessage(
|
|
kTypeNeighborTableLookup, kPhaseEnd, kResultFailure,
|
|
StringPrintf("Neighbor table entry for %s is not in a connected state "
|
|
"(actual state = 0x%2x)",
|
|
address_queried.ToString().c_str(), neighbor.state));
|
|
ReportResultAndStop(address_queried.Equals(connection_->gateway())
|
|
? kIssueGatewayNeighborEntryNotConnected
|
|
: kIssueServerNeighborEntryNotConnected);
|
|
return;
|
|
}
|
|
|
|
AddEventWithMessage(kTypeNeighborTableLookup, kPhaseEnd, kResultSuccess,
|
|
StringPrintf("Neighbor table entry found for %s",
|
|
address_queried.ToString().c_str()));
|
|
ReportResultAndStop(address_queried.Equals(connection_->gateway())
|
|
? kIssueGatewayNotResponding
|
|
: kIssueServerNotResponding);
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnNeighborTableRequestTimeout(
|
|
const IPAddress& address_queried) {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
AddEventWithMessage(kTypeNeighborTableLookup, kPhaseEnd, kResultFailure,
|
|
StringPrintf("Failed to find neighbor table entry for %s",
|
|
address_queried.ToString().c_str()));
|
|
ReportResultAndStop(address_queried.Equals(connection_->gateway())
|
|
? kIssueGatewayNoNeighborEntry
|
|
: kIssueServerNoNeighborEntry);
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnRouteQueryResponse(
|
|
int interface_index, const RoutingTableEntry& entry) {
|
|
SLOG(this, 3) << __func__ << "(interface " << interface_index << ")";
|
|
|
|
if (interface_index != connection_->interface_index()) {
|
|
SLOG(this, 3) << __func__
|
|
<< ": route query response not meant for this interface";
|
|
return;
|
|
}
|
|
|
|
route_query_timeout_callback_.Cancel();
|
|
AddEventWithMessage(
|
|
kTypeFindRoute, kPhaseEnd, kResultSuccess,
|
|
StringPrintf("Found route to %s (%s)", entry.dst.ToString().c_str(),
|
|
entry.gateway.IsDefault() ? "remote" : "local"));
|
|
if (!entry.gateway.IsDefault()) {
|
|
// We have a route to a remote destination, so ping the route gateway to
|
|
// check if we have a means of reaching this host.
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::PingHost,
|
|
weak_ptr_factory_.GetWeakPtr(), entry.gateway));
|
|
} else if (entry.dst.family() == IPAddress::kFamilyIPv4) {
|
|
// We have a route to a local IPv4 destination, so check for an ARP table
|
|
// entry.
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::FindArpTableEntry,
|
|
weak_ptr_factory_.GetWeakPtr(), entry.dst));
|
|
} else {
|
|
// We have a route to a local IPv6 destination, so check for a neighbor
|
|
// table entry.
|
|
dispatcher_->PostTask(Bind(&ConnectionDiagnostics::FindNeighborTableEntry,
|
|
weak_ptr_factory_.GetWeakPtr(), entry.dst));
|
|
}
|
|
}
|
|
|
|
void ConnectionDiagnostics::OnRouteQueryTimeout() {
|
|
SLOG(this, 3) << __func__;
|
|
|
|
AddEvent(kTypeFindRoute, kPhaseEnd, kResultFailure);
|
|
ReportResultAndStop(kIssueRouting);
|
|
}
|
|
|
|
bool ConnectionDiagnostics::DoesPreviousEventMatch(Type type, Phase phase,
|
|
Result result,
|
|
size_t num_events_ago) {
|
|
int event_index = diagnostic_events_.size() - 1 - num_events_ago;
|
|
if (event_index < 0) {
|
|
LOG(ERROR) << __func__ << ": requested event " << num_events_ago
|
|
<< " before the last event, but we only have "
|
|
<< diagnostic_events_.size() << " logged";
|
|
return false;
|
|
}
|
|
|
|
return (diagnostic_events_[event_index].type == type &&
|
|
diagnostic_events_[event_index].phase == phase &&
|
|
diagnostic_events_[event_index].result == result);
|
|
}
|
|
|
|
} // namespace shill
|