blob: 4154785f4169da7defeff3eb3d7a49a5ce72e959 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/dhcp/dhcp_config.h"
#include <memory>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/minijail/mock_minijail.h>
#include "shill/dbus_adaptor.h"
#include "shill/dhcp/mock_dhcp_provider.h"
#include "shill/dhcp/mock_dhcp_proxy.h"
#include "shill/event_dispatcher.h"
#include "shill/mock_control.h"
#include "shill/mock_glib.h"
#include "shill/mock_log.h"
#include "shill/mock_metrics.h"
#include "shill/mock_proxy_factory.h"
#include "shill/property_store_unittest.h"
#include "shill/testing.h"
using base::Bind;
using base::FilePath;
using base::ScopedTempDir;
using base::Unretained;
using chromeos::MockMinijail;
using std::string;
using std::unique_ptr;
using std::vector;
using testing::_;
using testing::AnyNumber;
using testing::ContainsRegex;
using testing::InvokeWithoutArgs;
using testing::Mock;
using testing::Return;
using testing::SetArgumentPointee;
using testing::Test;
namespace shill {
namespace {
const char kDeviceName[] = "eth0";
const char kDhcpMethod[] = "dhcp";
const char kLeaseFileSuffix[] = "leasefilesuffix";
const bool kHasLeaseSuffix = true;
} // namespace
class TestDHCPConfig : public DHCPConfig {
public:
TestDHCPConfig(ControlInterface* control_interface,
EventDispatcher* dispatcher,
DHCPProvider* provider,
const std::string& device_name,
const std::string& type,
const std::string& lease_file_suffix,
GLib* glib)
: DHCPConfig(control_interface,
dispatcher,
provider,
device_name,
type,
lease_file_suffix,
glib) {}
~TestDHCPConfig() {}
void ProcessEventSignal(const std::string& reason,
const Configuration& configuration) override {}
void ProcessStatusChangeSignal(const std::string& status) override {}
MOCK_METHOD0(ShouldFailOnAcquisitionTimeout, bool());
MOCK_METHOD0(ShouldKeepLeaseOnDisconnect, bool());
};
typedef scoped_refptr<TestDHCPConfig> TestDHCPConfigRefPtr;
class DHCPConfigTest : public PropertyStoreTest {
public:
DHCPConfigTest()
: proxy_(new MockDHCPProxy()),
minijail_(new MockMinijail()),
config_(new TestDHCPConfig(&control_,
dispatcher(),
&provider_,
kDeviceName,
kDhcpMethod,
kLeaseFileSuffix,
glib())) {}
virtual void SetUp() {
config_->proxy_factory_ = &proxy_factory_;
config_->minijail_ = minijail_.get();
}
virtual void TearDown() {
config_->proxy_factory_ = nullptr;
config_->minijail_ = nullptr;
}
void StopInstance() {
config_->Stop("In test");
}
TestDHCPConfigRefPtr CreateMockMinijailConfig(const string& lease_suffix);
protected:
static const int kPID;
static const unsigned int kTag;
unique_ptr<MockDHCPProxy> proxy_;
MockProxyFactory proxy_factory_;
MockControl control_;
unique_ptr<MockMinijail> minijail_;
TestDHCPConfigRefPtr config_;
MockDHCPProvider provider_;
};
const int DHCPConfigTest::kPID = 123456;
const unsigned int DHCPConfigTest::kTag = 77;
TestDHCPConfigRefPtr DHCPConfigTest::CreateMockMinijailConfig(
const string& lease_suffix) {
TestDHCPConfigRefPtr config(new TestDHCPConfig(&control_,
dispatcher(),
&provider_,
kDeviceName,
kDhcpMethod,
lease_suffix,
glib()));
config->minijail_ = minijail_.get();
return config;
}
TEST_F(DHCPConfigTest, InitProxy) {
static const char kService[] = ":1.200";
EXPECT_TRUE(proxy_.get());
EXPECT_FALSE(config_->proxy_.get());
EXPECT_CALL(proxy_factory_, CreateDHCPProxy(kService))
.WillOnce(ReturnAndReleasePointee(&proxy_));
config_->InitProxy(kService);
EXPECT_FALSE(proxy_.get());
EXPECT_TRUE(config_->proxy_.get());
config_->InitProxy(kService);
}
TEST_F(DHCPConfigTest, StartFail) {
EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).WillOnce(Return(false));
EXPECT_CALL(*glib(), ChildWatchAdd(_, _, _)).Times(0);
EXPECT_FALSE(config_->Start());
EXPECT_EQ(0, config_->pid_);
}
MATCHER_P(IsDHCPCDArgs, has_lease_suffix, "") {
if (string(arg[0]) != "/sbin/dhcpcd" ||
string(arg[1]) != "-B" ||
string(arg[2]) != "-q") {
return false;
}
int end_offset = 3;
string device_arg = has_lease_suffix ?
string(kDeviceName) + "=" + string(kLeaseFileSuffix) : kDeviceName;
return string(arg[end_offset]) == device_arg &&
arg[end_offset + 1] == nullptr;
}
TEST_F(DHCPConfigTest, StartWithoutLeaseSuffix) {
TestDHCPConfigRefPtr config = CreateMockMinijailConfig(kDeviceName);
EXPECT_CALL(*minijail_, RunAndDestroy(_, IsDHCPCDArgs(!kHasLeaseSuffix), _))
.WillOnce(Return(false));
EXPECT_FALSE(config->Start());
}
namespace {
class DHCPConfigCallbackTest : public DHCPConfigTest {
public:
virtual void SetUp() {
DHCPConfigTest::SetUp();
config_->RegisterUpdateCallback(
Bind(&DHCPConfigCallbackTest::SuccessCallback, Unretained(this)));
config_->RegisterFailureCallback(
Bind(&DHCPConfigCallbackTest::FailureCallback, Unretained(this)));
ip_config_ = config_;
}
MOCK_METHOD2(SuccessCallback,
void(const IPConfigRefPtr& ipconfig, bool new_lease_acquired));
MOCK_METHOD1(FailureCallback, void(const IPConfigRefPtr& ipconfig));
// The mock methods above take IPConfigRefPtr because this is the type
// that the registered callbacks take. This conversion of the DHCP
// config ref pointer eases our work in setting up expectations.
const IPConfigRefPtr& ConfigRef() { return ip_config_; }
private:
IPConfigRefPtr ip_config_;
};
void DoNothing() {}
} // namespace
TEST_F(DHCPConfigCallbackTest, NotifyFailure) {
EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
EXPECT_CALL(*this, FailureCallback(ConfigRef()));
config_->lease_acquisition_timeout_callback_.Reset(base::Bind(&DoNothing));
config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
config_->NotifyFailure();
Mock::VerifyAndClearExpectations(this);
EXPECT_TRUE(config_->properties().address.empty());
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
}
TEST_F(DHCPConfigCallbackTest, StoppedDuringFailureCallback) {
// Stop the DHCP config while it is calling the failure callback. We
// need to ensure that no callbacks are left running inadvertently as
// a result.
EXPECT_CALL(*this, FailureCallback(ConfigRef()))
.WillOnce(InvokeWithoutArgs(this, &DHCPConfigTest::StopInstance));
config_->NotifyFailure();
EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
}
TEST_F(DHCPConfigCallbackTest, StoppedDuringSuccessCallback) {
IPConfig::Properties properties;
properties.address = "1.2.3.4";
properties.lease_duration_seconds = 1;
// Stop the DHCP config while it is calling the success callback. This
// can happen if the device has a static IP configuration and releases
// the lease after accepting other network parameters from the DHCP
// IPConfig properties. We need to ensure that no callbacks are left
// running inadvertently as a result.
EXPECT_CALL(*this, SuccessCallback(ConfigRef(), true))
.WillOnce(InvokeWithoutArgs(this, &DHCPConfigTest::StopInstance));
config_->UpdateProperties(properties, true);
EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
}
TEST_F(DHCPConfigCallbackTest, ProcessAcquisitionTimeout) {
// Do not fail on acquisition timeout (e.g. ARP gateway is active).
EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
.WillOnce(Return(false));
EXPECT_CALL(*this, FailureCallback(_)).Times(0);
config_->ProcessAcquisitionTimeout();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(config_.get());
// Fail on acquisition timeout.
EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
.WillOnce(Return(true));
EXPECT_CALL(*this, FailureCallback(_)).Times(1);
config_->ProcessAcquisitionTimeout();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(config_.get());
}
TEST_F(DHCPConfigTest, ReleaseIP) {
config_->pid_ = 1 << 18; // Ensure unknown positive PID.
EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1);
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonDisconnect));
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, KeepLeaseOnDisconnect) {
config_->pid_ = 1 << 18; // Ensure unknown positive PID.
// Keep lease on disconnect (e.g. ARP gateway is enabled).
EXPECT_CALL(*config_.get(), ShouldKeepLeaseOnDisconnect())
.WillOnce(Return(true));
EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0);
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonDisconnect));
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, ReleaseLeaseOnDisconnect) {
config_->pid_ = 1 << 18; // Ensure unknown positive PID.
// Release lease on disconnect.
EXPECT_CALL(*config_.get(), ShouldKeepLeaseOnDisconnect())
.WillOnce(Return(false));
EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1);
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonDisconnect));
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, ReleaseIPStaticIPWithLease) {
config_->pid_ = 1 << 18; // Ensure unknown positive PID.
config_->is_lease_active_ = true;
EXPECT_CALL(*proxy_, Release(kDeviceName));
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonStaticIP));
EXPECT_EQ(nullptr, config_->proxy_.get());
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, ReleaseIPStaticIPWithoutLease) {
config_->pid_ = 1 << 18; // Ensure unknown positive PID.
config_->is_lease_active_ = false;
EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0);
MockDHCPProxy* proxy_pointer = proxy_.get();
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonStaticIP));
// Expect that proxy has not been released.
EXPECT_EQ(proxy_pointer, config_->proxy_.get());
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, RenewIP) {
EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).WillOnce(Return(false));
config_->pid_ = 0;
EXPECT_FALSE(config_->RenewIP()); // Expect a call to Start() if pid_ is 0.
Mock::VerifyAndClearExpectations(minijail_.get());
EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).Times(0);
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
config_->pid_ = 456;
EXPECT_FALSE(config_->RenewIP()); // Expect no crash with NULL proxy.
EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->RenewIP());
EXPECT_FALSE(config_->lease_acquisition_timeout_callback_.IsCancelled());
EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, RequestIP) {
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
config_->pid_ = 567;
EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->RenewIP());
EXPECT_FALSE(config_->lease_acquisition_timeout_callback_.IsCancelled());
config_->pid_ = 0;
}
TEST_F(DHCPConfigCallbackTest, RequestIPTimeout) {
EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
.WillOnce(Return(true));
EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
EXPECT_CALL(*this, FailureCallback(ConfigRef()));
config_->lease_acquisition_timeout_seconds_ = 0;
config_->pid_ = 567;
EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
config_->proxy_.reset(proxy_.release());
config_->RenewIP();
config_->dispatcher_->DispatchPendingEvents();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(config_.get());
config_->pid_ = 0;
}
TEST_F(DHCPConfigTest, Restart) {
const int kPID1 = 1 << 17; // Ensure unknown positive PID.
const int kPID2 = 987;
const unsigned int kTag1 = 11;
const unsigned int kTag2 = 22;
config_->pid_ = kPID1;
config_->child_watch_tag_ = kTag1;
EXPECT_CALL(provider_, UnbindPID(kPID1));
EXPECT_CALL(*glib(), SourceRemove(kTag1)).WillOnce(Return(true));
EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).WillOnce(
DoAll(SetArgumentPointee<2>(kPID2), Return(true)));
EXPECT_CALL(*glib(), ChildWatchAdd(kPID2, _, _)).WillOnce(Return(kTag2));
EXPECT_CALL(provider_, BindPID(kPID2, IsRefPtrTo(config_)));
EXPECT_TRUE(config_->Restart());
EXPECT_EQ(kPID2, config_->pid_);
EXPECT_EQ(kTag2, config_->child_watch_tag_);
config_->pid_ = 0;
config_->child_watch_tag_ = 0;
}
TEST_F(DHCPConfigTest, RestartNoClient) {
const int kPID = 777;
const unsigned int kTag = 66;
EXPECT_CALL(*glib(), SourceRemove(_)).Times(0);
EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).WillOnce(
DoAll(SetArgumentPointee<2>(kPID), Return(true)));
EXPECT_CALL(*glib(), ChildWatchAdd(kPID, _, _)).WillOnce(Return(kTag));
EXPECT_CALL(provider_, BindPID(kPID, IsRefPtrTo(config_)));
EXPECT_TRUE(config_->Restart());
EXPECT_EQ(kPID, config_->pid_);
EXPECT_EQ(kTag, config_->child_watch_tag_);
config_->pid_ = 0;
config_->child_watch_tag_ = 0;
}
TEST_F(DHCPConfigCallbackTest, StartTimeout) {
EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
.WillOnce(Return(true));
EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
EXPECT_CALL(*this, FailureCallback(ConfigRef()));
config_->lease_acquisition_timeout_seconds_ = 0;
config_->proxy_.reset(proxy_.release());
EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).WillOnce(Return(true));
config_->Start();
config_->dispatcher_->DispatchPendingEvents();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(config_.get());
}
TEST_F(DHCPConfigTest, Stop) {
const int kPID = 1 << 17; // Ensure unknown positive PID.
ScopedMockLog log;
EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
EXPECT_CALL(log, Log(_, _, ContainsRegex(
base::StringPrintf("Stopping.+%s", __func__))));
config_->pid_ = kPID;
config_->lease_acquisition_timeout_callback_.Reset(base::Bind(&DoNothing));
config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
EXPECT_CALL(provider_, UnbindPID(kPID));
config_->Stop(__func__);
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
EXPECT_FALSE(config_->pid_);
}
TEST_F(DHCPConfigTest, StopDuringRequestIP) {
config_->pid_ = 567;
EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
config_->proxy_.reset(proxy_.release());
EXPECT_TRUE(config_->RenewIP());
EXPECT_FALSE(config_->lease_acquisition_timeout_callback_.IsCancelled());
config_->pid_ = 0; // Keep Stop from killing a real process.
config_->Stop(__func__);
EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
}
TEST_F(DHCPConfigTest, SetProperty) {
::DBus::Error error;
// Ensure that an attempt to write a R/O property returns InvalidArgs error.
EXPECT_FALSE(DBusAdaptor::SetProperty(config_->mutable_store(),
kAddressProperty,
PropertyStoreTest::kStringV,
&error));
ASSERT_TRUE(error.is_set()); // name() may be invalid otherwise
EXPECT_EQ(invalid_args(), error.name());
}
} // namespace shill