//
// Copyright (C) 2013 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.
//
#ifndef SHILL_CONNECTION_HEALTH_CHECKER_H_
#define SHILL_CONNECTION_HEALTH_CHECKER_H_
#include <memory>
#include <string>
#include <vector>
#include <base/callback.h>
#include <base/cancelable_callback.h>
#include <base/macros.h>
#include <base/memory/scoped_vector.h>
#include <base/memory/weak_ptr.h>
#include <gtest/gtest_prod.h>
#include "shill/net/sockets.h"
#include "shill/refptr_types.h"
#include "shill/socket_info.h"
namespace shill {
class AsyncConnection;
class DNSClient;
class DNSClientFactory;
class Error;
class EventDispatcher;
class IPAddress;
class IPAddressStore;
class SocketInfoReader;
// The ConnectionHealthChecker class implements the facilities to test
// connectivity status on some connection asynchronously.
// In particular, the class can distinguish between three states of the
// connection:
// -(1)- No connectivity (TCP connection can not be established)
// -(2)- Partial connectivity (TCP connection can be established, but no data
// transfer)
// -(3)- Connectivity OK (TCP connection established, is healthy)
class ConnectionHealthChecker {
public:
enum Result {
// There was some problem in the setup of ConnctionHealthChecker.
// Could not attempt a tcp connection.
kResultUnknown,
// Failed to create TCP connection. Condition -(1)-.
kResultConnectionFailure,
// Failed to send data on TCP connection. Condition -(2)-.
kResultCongestedTxQueue,
// Condition -(3)-.
kResultSuccess
};
ConnectionHealthChecker(ConnectionRefPtr connection,
EventDispatcher* dispatcher,
IPAddressStore* remote_ips,
const base::Callback<void(Result)>& result_callback);
virtual ~ConnectionHealthChecker();
// A new ConnectionHealthChecker is created with a default URL to attempt the
// TCP connection with. Add a URL to try.
virtual void AddRemoteURL(const std::string& url_string);
// Name resolution can fail in conditions -(1)- and -(2)-. Add an IP address
// to attempt the TCP connection with.
virtual void AddRemoteIP(IPAddress ip);
// Change the associated Connection on the Device.
// This will restart any ongoing health check. Any ongoing DNS query will be
// dropped (not restarted).
virtual void SetConnection(ConnectionRefPtr connection);
// Start a connection health check. The health check involves one or more
// attempts at establishing and using a TCP connection. |result_callback_| is
// called with the final result of the check. |result_callback_| will always
// be called after a call to Start() unless Stop() is called in the meantime.
// |result_callback_| may be called before Start() completes.
//
// Calling Start() while a health check is in progress is a no-op.
virtual void Start();
// Stop the current health check. No callback is called as a side effect of
// this function.
//
// Calling Stop() on a Stop()ed health check is a no-op.
virtual void Stop();
static const char* ResultToString(Result result);
// Accessors.
const IPAddressStore* remote_ips() const { return remote_ips_; }
virtual bool health_check_in_progress() const;
protected:
// For unit-tests.
void set_dispatcher(EventDispatcher* dispatcher) {
dispatcher_ = dispatcher;
}
void set_sock_fd(int sock_fd) { sock_fd_ = sock_fd; }
int16_t num_connection_failures() const { return num_connection_failures_; }
void set_num_connection_failures(int16_t val) {
num_connection_failures_ = val;
}
int16_t num_tx_queue_polling_attempts() const {
return num_tx_queue_polling_attempts_;
}
void set_num_tx_queue_polling_attempts(int16_t val) {
num_tx_queue_polling_attempts_ = val;
}
int16_t num_congested_queue_detected() const {
return num_congested_queue_detected_;
}
void set_num_congested_queue_detected(int16_t val) {
num_congested_queue_detected_ = val;
}
int16_t num_successful_sends() const { return num_successful_sends_; }
void set_num_successful_sends(int16_t val) {
num_successful_sends_ = val;
}
void set_old_transmit_queue_value(uint64_t val) {
old_transmit_queue_value_ = val;
}
Result health_check_result() const { return health_check_result_; }
AsyncConnection* tcp_connection() const { return tcp_connection_.get(); }
Connection* connection() const { return connection_.get(); }
private:
friend class ConnectionHealthCheckerTest;
FRIEND_TEST(ConnectionHealthCheckerTest, GarbageCollectDNSClients);
FRIEND_TEST(ConnectionHealthCheckerTest, GetSocketInfo);
FRIEND_TEST(ConnectionHealthCheckerTest, NextHealthCheckSample);
FRIEND_TEST(ConnectionHealthCheckerTest, OnConnectionComplete);
FRIEND_TEST(ConnectionHealthCheckerTest, SetConnection);
FRIEND_TEST(ConnectionHealthCheckerTest, VerifySentData);
// List of static IPs for connection health check.
static const char* kDefaultRemoteIPPool[];
// Time to wait for DNS server.
static const int kDNSTimeoutMilliseconds;
static const int kInvalidSocket;
// After |kMaxFailedConnectionAttempts| failed attempts to connect, give up
// health check and return failure.
static const int kMaxFailedConnectionAttempts;
// After sending a small amount of data, attempt |kMaxSentDataPollingAttempts|
// times to see if the data was sent successfully.
static const int kMaxSentDataPollingAttempts;
// After |kMinCongestedQueueAttempts| to send data indicate a congested tx
// queue, finish health check and report a congested queue.
static const int kMinCongestedQueueAttempts;
// After sending data |kMinSuccessfulAttempts| times succesfully, finish
// health check and report a healthy connection.
static const int kMinSuccessfulSendAttempts;
// Number of DNS queries to be spawned when a new remote URL is added.
static const int kNumDNSQueries;
static const uint16_t kRemotePort;
// Time to wait before testing successful data transfer / disconnect after
// request is made on the device.
static const int kTCPStateUpdateWaitMilliseconds;
// Callback for DnsClient
void GetDNSResult(const Error& error, const IPAddress& ip);
void GarbageCollectDNSClients();
// Start a new AsyncConnection with callback set to OnConnectionComplete().
void NextHealthCheckSample();
void ReportResult();
// Callback for AsyncConnection.
// Observe the setup connection to test health state
void OnConnectionComplete(bool success, int sock_fd);
void VerifySentData();
bool GetSocketInfo(int sock_fd, SocketInfo* sock_info);
void SetSocketDescriptor(int sock_fd);
void ClearSocketDescriptor();
// The connection on which the health check is being run.
ConnectionRefPtr connection_;
EventDispatcher* dispatcher_;
// Set of IPs to create TCP connection with for the health check.
IPAddressStore* remote_ips_;
base::Callback<void(Result)> result_callback_;
std::unique_ptr<Sockets> socket_;
base::WeakPtrFactory<ConnectionHealthChecker> weak_ptr_factory_;
// Callback passed to |tcp_connection_| to report an established TCP
// connection.
const base::Callback<void(bool, int)> connection_complete_callback_;
// Active TCP connection during health check.
std::unique_ptr<AsyncConnection> tcp_connection_;
const base::Callback<void(void)> report_result_;
// Active socket for |tcp_connection_| during an active health check.
int sock_fd_;
// Interface to read TCP connection information from the system.
std::unique_ptr<SocketInfoReader> socket_info_reader_;
DNSClientFactory* dns_client_factory_;
ScopedVector<DNSClient> dns_clients_;
const base::Callback<void(const Error&, const IPAddress&)>
dns_client_callback_;
// Store the old value of the transmit queue to verify that data sent on the
// connection is actually transmitted.
uint64_t old_transmit_queue_value_;
// Callback to post a delayed check on whether data sent on the TCP connection
// was successfully transmitted.
base::CancelableClosure verify_sent_data_callback_;
bool health_check_in_progress_;
// Number of connection failures in currently active health check.
int16_t num_connection_failures_;
// Number of times we have checked the tx-queue for the current send attempt.
int16_t num_tx_queue_polling_attempts_;
// Number of out of credit scenarios detected in current health check.
int16_t num_congested_queue_detected_;
// Number of successful send attempts currently active health check.
int16_t num_successful_sends_;
// Snooze time while polling for updated /proc/tcpinfo
int tcp_state_update_wait_milliseconds_;
// Temporarily store the result of health check so that |report_result_|
// can report it.
Result health_check_result_;
DISALLOW_COPY_AND_ASSIGN(ConnectionHealthChecker);
};
} // namespace shill
#endif // SHILL_CONNECTION_HEALTH_CHECKER_H_