// // 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/dhcp/dhcp_config.h" #include #include #include #include #include #include #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_log.h" #include "shill/mock_metrics.h" #include "shill/mock_process_manager.h" #include "shill/property_store_unittest.h" #include "shill/testing.h" using base::Bind; using base::FilePath; using base::ScopedTempDir; using base::Unretained; 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) : DHCPConfig(control_interface, dispatcher, provider, device_name, type, lease_file_suffix) {} ~TestDHCPConfig() {} void ProcessEventSignal(const std::string& reason, const KeyValueStore& configuration) override {} void ProcessStatusChangeSignal(const std::string& status) override {} MOCK_METHOD0(ShouldFailOnAcquisitionTimeout, bool()); MOCK_METHOD0(ShouldKeepLeaseOnDisconnect, bool()); }; typedef scoped_refptr TestDHCPConfigRefPtr; class DHCPConfigTest : public PropertyStoreTest { public: DHCPConfigTest() : proxy_(new MockDHCPProxy()), config_(new TestDHCPConfig(&control_, dispatcher(), &provider_, kDeviceName, kDhcpMethod, kLeaseFileSuffix)) {} virtual void SetUp() { config_->process_manager_ = &process_manager_; } void StopInstance() { config_->Stop("In test"); } TestDHCPConfigRefPtr CreateMockMinijailConfig(const string& lease_suffix); protected: static const int kPID; unique_ptr proxy_; MockControl control_; MockProcessManager process_manager_; TestDHCPConfigRefPtr config_; MockDHCPProvider provider_; }; const int DHCPConfigTest::kPID = 123456; TestDHCPConfigRefPtr DHCPConfigTest::CreateMockMinijailConfig( const string& lease_suffix) { TestDHCPConfigRefPtr config(new TestDHCPConfig(&control_, dispatcher(), &provider_, kDeviceName, kDhcpMethod, lease_suffix)); config->process_manager_ = &process_manager_; return config; } TEST_F(DHCPConfigTest, InitProxy) { static const char kService[] = ":1.200"; EXPECT_TRUE(proxy_.get()); EXPECT_FALSE(config_->proxy_.get()); EXPECT_CALL(control_, 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(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) .WillOnce(Return(-1)); EXPECT_FALSE(config_->Start()); EXPECT_EQ(0, config_->pid_); } MATCHER_P(IsDHCPCDArgs, has_lease_suffix, "") { if (arg[0] != "-B" || arg[1] != "-q") { return false; } int end_offset = 2; string device_arg = has_lease_suffix ? string(kDeviceName) + "=" + string(kLeaseFileSuffix) : kDeviceName; return arg[end_offset] == device_arg; } TEST_F(DHCPConfigTest, StartWithoutLeaseSuffix) { TestDHCPConfigRefPtr config = CreateMockMinijailConfig(kDeviceName); EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, IsDHCPCDArgs(!kHasLeaseSuffix), _, _, _, _)) .WillOnce(Return(-1)); 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(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) .WillOnce(Return(-1)); config_->pid_ = 0; EXPECT_FALSE(config_->RenewIP()); // Expect a call to Start() if pid_ is 0. Mock::VerifyAndClearExpectations(&process_manager_); EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) .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; config_->pid_ = kPID1; EXPECT_CALL(provider_, UnbindPID(kPID1)); EXPECT_CALL(process_manager_, StopProcess(kPID1)).WillOnce(Return(true)); EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) .WillOnce(Return(kPID2)); EXPECT_CALL(provider_, BindPID(kPID2, IsRefPtrTo(config_))); EXPECT_TRUE(config_->Restart()); EXPECT_EQ(kPID2, config_->pid_); config_->pid_ = 0; } TEST_F(DHCPConfigTest, RestartNoClient) { const int kPID = 777; EXPECT_CALL(process_manager_, StopProcess(_)).Times(0); EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) .WillOnce(Return(kPID)); EXPECT_CALL(provider_, BindPID(kPID, IsRefPtrTo(config_))); EXPECT_TRUE(config_->Restart()); EXPECT_EQ(kPID, config_->pid_); config_->pid_ = 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(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) .WillOnce(Return(0)); 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) { Error error; // Ensure that an attempt to write a R/O property returns InvalidArgs error. EXPECT_FALSE(config_->mutable_store()->SetAnyProperty( kAddressProperty, PropertyStoreTest::kStringV, &error)); EXPECT_TRUE(error.IsFailure()); EXPECT_EQ(Error::kInvalidArguments, error.type()); } } // namespace shill