439 lines
14 KiB
C++
439 lines
14 KiB
C++
//
|
|
// Copyright (C) 2012 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/dns_client.h"
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <base/bind.h>
|
|
|
|
#include "shill/error.h"
|
|
#include "shill/event_dispatcher.h"
|
|
#include "shill/mock_ares.h"
|
|
#include "shill/mock_control.h"
|
|
#include "shill/mock_event_dispatcher.h"
|
|
#include "shill/net/io_handler.h"
|
|
#include "shill/net/mock_time.h"
|
|
#include "shill/testing.h"
|
|
|
|
using base::Bind;
|
|
using base::Unretained;
|
|
using std::string;
|
|
using std::vector;
|
|
using testing::_;
|
|
using testing::DoAll;
|
|
using testing::Not;
|
|
using testing::Return;
|
|
using testing::ReturnArg;
|
|
using testing::ReturnNew;
|
|
using testing::Test;
|
|
using testing::SetArgumentPointee;
|
|
using testing::StrEq;
|
|
using testing::StrictMock;
|
|
|
|
namespace shill {
|
|
|
|
namespace {
|
|
const char kGoodName[] = "all-systems.mcast.net";
|
|
const char kResult[] = "224.0.0.1";
|
|
const char kGoodServer[] = "8.8.8.8";
|
|
const char kBadServer[] = "10.9xx8.7";
|
|
const char kNetworkInterface[] = "eth0";
|
|
char kReturnAddressList0[] = { static_cast<char>(224), 0, 0, 1 };
|
|
char* kReturnAddressList[] = { kReturnAddressList0, nullptr };
|
|
char kFakeAresChannelData = 0;
|
|
const ares_channel kAresChannel =
|
|
reinterpret_cast<ares_channel>(&kFakeAresChannelData);
|
|
const int kAresFd = 10203;
|
|
const int kAresTimeoutMS = 2000; // ARES transaction timeout
|
|
const int kAresWaitMS = 1000; // Time period ARES asks caller to wait
|
|
} // namespace
|
|
|
|
class DNSClientTest : public Test {
|
|
public:
|
|
DNSClientTest()
|
|
: ares_result_(ARES_SUCCESS), address_result_(IPAddress::kFamilyUnknown) {
|
|
time_val_.tv_sec = 0;
|
|
time_val_.tv_usec = 0;
|
|
ares_timeout_.tv_sec = kAresWaitMS / 1000;
|
|
ares_timeout_.tv_usec = (kAresWaitMS % 1000) * 1000;
|
|
hostent_.h_addrtype = IPAddress::kFamilyIPv4;
|
|
hostent_.h_length = sizeof(kReturnAddressList0);
|
|
hostent_.h_addr_list = kReturnAddressList;
|
|
}
|
|
|
|
virtual void SetUp() {
|
|
EXPECT_CALL(time_, GetTimeMonotonic(_))
|
|
.WillRepeatedly(DoAll(SetArgumentPointee<0>(time_val_), Return(0)));
|
|
SetInActive();
|
|
}
|
|
|
|
virtual void TearDown() {
|
|
// We need to make sure the dns_client instance releases ares_
|
|
// before the destructor for DNSClientTest deletes ares_.
|
|
if (dns_client_.get()) {
|
|
dns_client_->Stop();
|
|
}
|
|
}
|
|
|
|
void AdvanceTime(int time_ms) {
|
|
struct timeval adv_time = { time_ms/1000, (time_ms % 1000) * 1000 };
|
|
timeradd(&time_val_, &adv_time, &time_val_);
|
|
EXPECT_CALL(time_, GetTimeMonotonic(_))
|
|
.WillRepeatedly(DoAll(SetArgumentPointee<0>(time_val_), Return(0)));
|
|
}
|
|
|
|
void CallReplyCB() {
|
|
dns_client_->ReceiveDNSReplyCB(dns_client_.get(), ares_result_, 0,
|
|
&hostent_);
|
|
}
|
|
|
|
void CallDNSRead() {
|
|
dns_client_->HandleDNSRead(kAresFd);
|
|
}
|
|
|
|
void CallDNSWrite() {
|
|
dns_client_->HandleDNSWrite(kAresFd);
|
|
}
|
|
|
|
void CallTimeout() {
|
|
dns_client_->HandleTimeout();
|
|
}
|
|
|
|
void CallCompletion() {
|
|
dns_client_->HandleCompletion();
|
|
}
|
|
|
|
void CreateClient(const vector<string>& dns_servers, int timeout_ms) {
|
|
dns_client_.reset(new DNSClient(IPAddress::kFamilyIPv4,
|
|
kNetworkInterface,
|
|
dns_servers,
|
|
timeout_ms,
|
|
&dispatcher_,
|
|
callback_target_.callback()));
|
|
dns_client_->ares_ = &ares_;
|
|
dns_client_->time_ = &time_;
|
|
}
|
|
|
|
void SetActive() {
|
|
// Returns that socket kAresFd is readable.
|
|
EXPECT_CALL(ares_, GetSock(_, _, _))
|
|
.WillRepeatedly(DoAll(SetArgumentPointee<1>(kAresFd), Return(1)));
|
|
EXPECT_CALL(ares_, Timeout(_, _, _))
|
|
.WillRepeatedly(
|
|
DoAll(SetArgumentPointee<2>(ares_timeout_), ReturnArg<2>()));
|
|
}
|
|
|
|
void SetInActive() {
|
|
EXPECT_CALL(ares_, GetSock(_, _, _))
|
|
.WillRepeatedly(Return(0));
|
|
EXPECT_CALL(ares_, Timeout(_, _, _))
|
|
.WillRepeatedly(ReturnArg<1>());
|
|
}
|
|
|
|
void SetupRequest(const string& name, const string& server) {
|
|
vector<string> dns_servers;
|
|
dns_servers.push_back(server);
|
|
CreateClient(dns_servers, kAresTimeoutMS);
|
|
// These expectations are fulfilled when dns_client_->Start() is called.
|
|
EXPECT_CALL(ares_, InitOptions(_, _, _))
|
|
.WillOnce(DoAll(SetArgumentPointee<0>(kAresChannel),
|
|
Return(ARES_SUCCESS)));
|
|
EXPECT_CALL(ares_, SetServersCsv(_, _))
|
|
.WillOnce(Return(ARES_SUCCESS));
|
|
EXPECT_CALL(ares_, SetLocalDev(kAresChannel, StrEq(kNetworkInterface)))
|
|
.Times(1);
|
|
EXPECT_CALL(ares_, GetHostByName(kAresChannel, StrEq(name), _, _, _));
|
|
}
|
|
|
|
void StartValidRequest() {
|
|
SetupRequest(kGoodName, kGoodServer);
|
|
EXPECT_CALL(dispatcher_,
|
|
CreateReadyHandler(kAresFd, IOHandler::kModeInput, _))
|
|
.WillOnce(ReturnNew<IOHandler>());
|
|
SetActive();
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
Error error;
|
|
ASSERT_TRUE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_TRUE(error.IsSuccess());
|
|
EXPECT_CALL(ares_, Destroy(kAresChannel));
|
|
}
|
|
|
|
void TestValidCompletion() {
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, kAresFd, ARES_SOCKET_BAD))
|
|
.WillOnce(InvokeWithoutArgs(this, &DNSClientTest::CallReplyCB));
|
|
ExpectPostCompletionTask();
|
|
CallDNSRead();
|
|
|
|
// Make sure that the address value is correct as held in the DNSClient.
|
|
ASSERT_TRUE(dns_client_->address_.IsValid());
|
|
IPAddress ipaddr(dns_client_->address_.family());
|
|
ASSERT_TRUE(ipaddr.SetAddressFromString(kResult));
|
|
EXPECT_TRUE(ipaddr.Equals(dns_client_->address_));
|
|
|
|
// Make sure the callback gets called with a success result, and save
|
|
// the callback address argument in |address_result_|.
|
|
EXPECT_CALL(callback_target_, CallTarget(IsSuccess(), _))
|
|
.WillOnce(Invoke(this, &DNSClientTest::SaveCallbackArgs));
|
|
CallCompletion();
|
|
|
|
// Make sure the address was successfully passed to the callback.
|
|
EXPECT_TRUE(ipaddr.Equals(address_result_));
|
|
EXPECT_TRUE(dns_client_->address_.IsDefault());
|
|
}
|
|
|
|
void SaveCallbackArgs(const Error& error, const IPAddress& address) {
|
|
error_result_.CopyFrom(error);
|
|
address_result_ = address;
|
|
}
|
|
|
|
void ExpectPostCompletionTask() {
|
|
EXPECT_CALL(dispatcher_, PostTask(_));
|
|
}
|
|
|
|
void ExpectReset() {
|
|
EXPECT_TRUE(dns_client_->address_.family() == IPAddress::kFamilyIPv4);
|
|
EXPECT_TRUE(dns_client_->address_.IsDefault());
|
|
EXPECT_FALSE(dns_client_->resolver_state_.get());
|
|
}
|
|
|
|
protected:
|
|
class DNSCallbackTarget {
|
|
public:
|
|
DNSCallbackTarget()
|
|
: callback_(Bind(&DNSCallbackTarget::CallTarget, Unretained(this))) {}
|
|
|
|
MOCK_METHOD2(CallTarget, void(const Error& error,
|
|
const IPAddress& address));
|
|
const DNSClient::ClientCallback& callback() { return callback_; }
|
|
|
|
private:
|
|
DNSClient::ClientCallback callback_;
|
|
};
|
|
|
|
std::unique_ptr<DNSClient> dns_client_;
|
|
StrictMock<MockEventDispatcher> dispatcher_;
|
|
string queued_request_;
|
|
StrictMock<DNSCallbackTarget> callback_target_;
|
|
StrictMock<MockAres> ares_;
|
|
StrictMock<MockTime> time_;
|
|
struct timeval time_val_;
|
|
struct timeval ares_timeout_;
|
|
struct hostent hostent_;
|
|
int ares_result_;
|
|
Error error_result_;
|
|
IPAddress address_result_;
|
|
};
|
|
|
|
class SentinelIOHandler : public IOHandler {
|
|
public:
|
|
MOCK_METHOD0(Die, void());
|
|
virtual ~SentinelIOHandler() { Die(); }
|
|
};
|
|
|
|
TEST_F(DNSClientTest, Constructor) {
|
|
vector<string> dns_servers;
|
|
dns_servers.push_back(kGoodServer);
|
|
CreateClient(dns_servers, kAresTimeoutMS);
|
|
ExpectReset();
|
|
}
|
|
|
|
// Receive error because no DNS servers were specified.
|
|
TEST_F(DNSClientTest, NoServers) {
|
|
CreateClient(vector<string>(), kAresTimeoutMS);
|
|
Error error;
|
|
EXPECT_FALSE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_EQ(Error::kInvalidArguments, error.type());
|
|
}
|
|
|
|
// Setup error because SetServersCsv failed due to invalid DNS servers.
|
|
TEST_F(DNSClientTest, SetServersCsvInvalidServer) {
|
|
vector<string> dns_servers;
|
|
dns_servers.push_back(kBadServer);
|
|
CreateClient(dns_servers, kAresTimeoutMS);
|
|
EXPECT_CALL(ares_, InitOptions(_, _, _))
|
|
.WillOnce(Return(ARES_SUCCESS));
|
|
EXPECT_CALL(ares_, SetServersCsv(_, _))
|
|
.WillOnce(Return(ARES_EBADSTR));
|
|
Error error;
|
|
EXPECT_FALSE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_EQ(Error::kOperationFailed, error.type());
|
|
}
|
|
|
|
// Setup error because InitOptions failed.
|
|
TEST_F(DNSClientTest, InitOptionsFailure) {
|
|
vector<string> dns_servers;
|
|
dns_servers.push_back(kGoodServer);
|
|
CreateClient(dns_servers, kAresTimeoutMS);
|
|
EXPECT_CALL(ares_, InitOptions(_, _, _))
|
|
.WillOnce(Return(ARES_EBADFLAGS));
|
|
Error error;
|
|
EXPECT_FALSE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_EQ(Error::kOperationFailed, error.type());
|
|
}
|
|
|
|
// Fail a second request because one is already in progress.
|
|
TEST_F(DNSClientTest, MultipleRequest) {
|
|
StartValidRequest();
|
|
Error error;
|
|
ASSERT_FALSE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_EQ(Error::kInProgress, error.type());
|
|
}
|
|
|
|
TEST_F(DNSClientTest, GoodRequest) {
|
|
StartValidRequest();
|
|
TestValidCompletion();
|
|
}
|
|
|
|
TEST_F(DNSClientTest, GoodRequestWithTimeout) {
|
|
StartValidRequest();
|
|
// Insert an intermediate HandleTimeout callback.
|
|
AdvanceTime(kAresWaitMS);
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD));
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
CallTimeout();
|
|
AdvanceTime(kAresWaitMS);
|
|
TestValidCompletion();
|
|
}
|
|
|
|
TEST_F(DNSClientTest, GoodRequestWithDNSRead) {
|
|
StartValidRequest();
|
|
// Insert an intermediate HandleDNSRead callback.
|
|
AdvanceTime(kAresWaitMS);
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, kAresFd, ARES_SOCKET_BAD));
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
CallDNSRead();
|
|
AdvanceTime(kAresWaitMS);
|
|
TestValidCompletion();
|
|
}
|
|
|
|
TEST_F(DNSClientTest, GoodRequestWithDNSWrite) {
|
|
StartValidRequest();
|
|
// Insert an intermediate HandleDNSWrite callback.
|
|
AdvanceTime(kAresWaitMS);
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, ARES_SOCKET_BAD, kAresFd));
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
CallDNSWrite();
|
|
AdvanceTime(kAresWaitMS);
|
|
TestValidCompletion();
|
|
}
|
|
|
|
// Failure due to the timeout occurring during first call to RefreshHandles.
|
|
TEST_F(DNSClientTest, TimeoutFirstRefresh) {
|
|
SetupRequest(kGoodName, kGoodServer);
|
|
struct timeval init_time_val = time_val_;
|
|
AdvanceTime(kAresTimeoutMS);
|
|
EXPECT_CALL(time_, GetTimeMonotonic(_))
|
|
.WillOnce(DoAll(SetArgumentPointee<0>(init_time_val), Return(0)))
|
|
.WillRepeatedly(DoAll(SetArgumentPointee<0>(time_val_), Return(0)));
|
|
EXPECT_CALL(callback_target_, CallTarget(Not(IsSuccess()), _))
|
|
.Times(0);
|
|
EXPECT_CALL(ares_, Destroy(kAresChannel));
|
|
Error error;
|
|
// Expect the DNSClient to post a completion task. However this task will
|
|
// never run since the Stop() gets called before returning. We confirm
|
|
// that the task indeed gets canceled below in ExpectReset().
|
|
ExpectPostCompletionTask();
|
|
ASSERT_FALSE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_EQ(Error::kOperationTimeout, error.type());
|
|
EXPECT_EQ(string(DNSClient::kErrorTimedOut), error.message());
|
|
ExpectReset();
|
|
}
|
|
|
|
// Failed request due to timeout within the dns_client.
|
|
TEST_F(DNSClientTest, TimeoutDispatcherEvent) {
|
|
StartValidRequest();
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel,
|
|
ARES_SOCKET_BAD, ARES_SOCKET_BAD));
|
|
AdvanceTime(kAresTimeoutMS);
|
|
ExpectPostCompletionTask();
|
|
CallTimeout();
|
|
EXPECT_CALL(callback_target_, CallTarget(
|
|
ErrorIs(Error::kOperationTimeout, DNSClient::kErrorTimedOut), _));
|
|
CallCompletion();
|
|
}
|
|
|
|
// Failed request due to timeout reported by ARES.
|
|
TEST_F(DNSClientTest, TimeoutFromARES) {
|
|
StartValidRequest();
|
|
AdvanceTime(kAresWaitMS);
|
|
ares_result_ = ARES_ETIMEOUT;
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD))
|
|
.WillOnce(InvokeWithoutArgs(this, &DNSClientTest::CallReplyCB));
|
|
ExpectPostCompletionTask();
|
|
CallTimeout();
|
|
EXPECT_CALL(callback_target_, CallTarget(
|
|
ErrorIs(Error::kOperationTimeout, DNSClient::kErrorTimedOut), _));
|
|
CallCompletion();
|
|
}
|
|
|
|
// Failed request due to "host not found" reported by ARES.
|
|
TEST_F(DNSClientTest, HostNotFound) {
|
|
StartValidRequest();
|
|
AdvanceTime(kAresWaitMS);
|
|
ares_result_ = ARES_ENOTFOUND;
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, kAresFd, ARES_SOCKET_BAD))
|
|
.WillOnce(InvokeWithoutArgs(this, &DNSClientTest::CallReplyCB));
|
|
ExpectPostCompletionTask();
|
|
CallDNSRead();
|
|
EXPECT_CALL(callback_target_, CallTarget(
|
|
ErrorIs(Error::kOperationFailed, DNSClient::kErrorNotFound), _));
|
|
CallCompletion();
|
|
}
|
|
|
|
// Make sure IOHandles are deallocated when GetSock() reports them gone.
|
|
TEST_F(DNSClientTest, IOHandleDeallocGetSock) {
|
|
SetupRequest(kGoodName, kGoodServer);
|
|
// This isn't any kind of scoped/ref pointer because we are tracking dealloc.
|
|
SentinelIOHandler* io_handler = new SentinelIOHandler();
|
|
EXPECT_CALL(dispatcher_,
|
|
CreateReadyHandler(kAresFd, IOHandler::kModeInput, _))
|
|
.WillOnce(Return(io_handler));
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
SetActive();
|
|
Error error;
|
|
ASSERT_TRUE(dns_client_->Start(kGoodName, &error));
|
|
AdvanceTime(kAresWaitMS);
|
|
SetInActive();
|
|
EXPECT_CALL(*io_handler, Die());
|
|
EXPECT_CALL(ares_, ProcessFd(kAresChannel, kAresFd, ARES_SOCKET_BAD));
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
CallDNSRead();
|
|
EXPECT_CALL(ares_, Destroy(kAresChannel));
|
|
}
|
|
|
|
// Make sure IOHandles are deallocated when Stop() is called.
|
|
TEST_F(DNSClientTest, IOHandleDeallocStop) {
|
|
SetupRequest(kGoodName, kGoodServer);
|
|
// This isn't any kind of scoped/ref pointer because we are tracking dealloc.
|
|
SentinelIOHandler* io_handler = new SentinelIOHandler();
|
|
EXPECT_CALL(dispatcher_,
|
|
CreateReadyHandler(kAresFd, IOHandler::kModeInput, _))
|
|
.WillOnce(Return(io_handler));
|
|
EXPECT_CALL(dispatcher_, PostDelayedTask(_, kAresWaitMS));
|
|
SetActive();
|
|
Error error;
|
|
ASSERT_TRUE(dns_client_->Start(kGoodName, &error));
|
|
EXPECT_CALL(*io_handler, Die());
|
|
EXPECT_CALL(ares_, Destroy(kAresChannel));
|
|
dns_client_->Stop();
|
|
}
|
|
|
|
} // namespace shill
|