| // Copyright 2018 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/network/dhcp_controller.h" |
| |
| #include <memory> |
| #include <string> |
| #include <sys/time.h> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/time/time.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "shill/event_dispatcher.h" |
| #include "shill/logging.h" |
| #include "shill/mock_log.h" |
| #include "shill/mock_metrics.h" |
| #include "shill/mock_process_manager.h" |
| #include "shill/net/mock_time.h" |
| #include "shill/network/dhcpv4_config.h" |
| #include "shill/network/mock_dhcp_provider.h" |
| #include "shill/network/mock_dhcp_proxy.h" |
| #include "shill/store/property_store_test.h" |
| #include "shill/technology.h" |
| #include "shill/test_event_dispatcher.h" |
| #include "shill/testing.h" |
| |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::ByMove; |
| using testing::ContainsRegex; |
| using testing::DoAll; |
| using testing::EndsWith; |
| using testing::InvokeWithoutArgs; |
| using testing::Mock; |
| using testing::Return; |
| using testing::SaveArg; |
| using testing::SetArgPointee; |
| using testing::WithArg; |
| |
| namespace shill { |
| |
| namespace { |
| constexpr bool kArpGateway = true; |
| constexpr char kDeviceName[] = "eth0"; |
| constexpr char kHostName[] = "hostname"; |
| constexpr char kLeaseFileSuffix[] = "leasefilesuffix"; |
| constexpr bool kHasHostname = true; |
| constexpr bool kHasLeaseSuffix = true; |
| constexpr uint32_t kTimeNow = 10; |
| constexpr uint32_t kLeaseDuration = 5; |
| |
| MATCHER_P(IsWeakPtrTo, address, "") { |
| return arg.get() == address; |
| } |
| } // namespace |
| |
| class DHCPControllerTest : public PropertyStoreTest { |
| public: |
| DHCPControllerTest() |
| : proxy_(new MockDHCPProxy()), |
| controller_(new DHCPController(control_interface(), |
| dispatcher(), |
| &provider_, |
| kDeviceName, |
| kLeaseFileSuffix, |
| kArpGateway, |
| kHostName, |
| Technology::kUnknown, |
| &metrics_)) { |
| controller_->time_ = &time_; |
| } |
| |
| void SetUp() override { controller_->process_manager_ = &process_manager_; } |
| |
| void SetDHCPVerboseLog() { |
| ScopeLogger::GetInstance()->EnableScopesByName("dhcp"); |
| ScopeLogger::GetInstance()->set_verbose_level(3); |
| } |
| |
| void ResetDHCPVerboseLog() { |
| ScopeLogger::GetInstance()->EnableScopesByName("-dhcp"); |
| ScopeLogger::GetInstance()->set_verbose_level(0); |
| } |
| |
| // Sets the current time returned by time_.GetTimeBoottime() to |second|. |
| void SetCurrentTimeToSecond(uint32_t second) { |
| struct timeval current = {static_cast<__time_t>(second), 0}; |
| EXPECT_CALL(time_, GetTimeBoottime(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(current), Return(0))); |
| } |
| |
| bool StartInstance() { return controller_->Start(); } |
| |
| void StopInstance() { controller_->Stop("In test"); } |
| |
| void InvokeOnIPConfigUpdated(const IPConfig::Properties& properties, |
| bool new_lease_acquired) { |
| controller_->OnIPConfigUpdated(properties, new_lease_acquired); |
| } |
| |
| bool ShouldFailOnAcquisitionTimeout() { |
| return controller_->ShouldFailOnAcquisitionTimeout(); |
| } |
| |
| void SetShouldFailOnAcquisitionTimeout(bool value) { |
| controller_->is_gateway_arp_active_ = !value; |
| } |
| |
| bool ShouldKeepLeaseOnDisconnect() { |
| return controller_->ShouldKeepLeaseOnDisconnect(); |
| } |
| |
| void SetShouldKeepLeaseOnDisconnect(bool value) { |
| controller_->arp_gateway_ = value; |
| } |
| |
| void CreateMockMinijailConfig(const std::string& hostname, |
| const std::string& lease_suffix, |
| bool arp_gateway); |
| |
| protected: |
| static constexpr int kPID = 123456; |
| |
| std::unique_ptr<MockDHCPProxy> proxy_; |
| MockProcessManager process_manager_; |
| MockTime time_; |
| std::unique_ptr<DHCPController> controller_; |
| MockDHCPProvider provider_; |
| MockMetrics metrics_; |
| }; |
| |
| // Resets |controller_| to an instance initiated with the given parameters, |
| // which can be used in the tests for verifying parameters to invoke minijail. |
| void DHCPControllerTest::CreateMockMinijailConfig( |
| const std::string& hostname, |
| const std::string& lease_suffix, |
| bool arp_gateway) { |
| controller_.reset(new DHCPController( |
| control_interface(), dispatcher(), &provider_, kDeviceName, lease_suffix, |
| arp_gateway, hostname, Technology::kUnknown, metrics())); |
| controller_->process_manager_ = &process_manager_; |
| } |
| |
| TEST_F(DHCPControllerTest, InitProxy) { |
| static const char kService[] = ":1.200"; |
| EXPECT_NE(nullptr, proxy_); |
| EXPECT_EQ(nullptr, controller_->proxy_); |
| EXPECT_CALL(*control_interface(), CreateDHCPProxy(kService)) |
| .WillOnce(Return(ByMove(std::move(proxy_)))); |
| controller_->InitProxy(kService); |
| EXPECT_EQ(nullptr, proxy_); |
| EXPECT_NE(nullptr, controller_->proxy_); |
| |
| controller_->InitProxy(kService); |
| } |
| |
| TEST_F(DHCPControllerTest, StartFail) { |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(-1)); |
| EXPECT_FALSE(controller_->Start()); |
| EXPECT_EQ(0, controller_->pid_); |
| } |
| |
| MATCHER_P3(IsDHCPCDArgs, has_hostname, has_arp_gateway, has_lease_suffix, "") { |
| if (arg[0] != "-B" || arg[1] != "-i" || arg[2] != "chromeos" || |
| arg[3] != "-q" || arg[4] != "-4") { |
| return false; |
| } |
| |
| int end_offset = 5; |
| if (has_hostname) { |
| if (arg[end_offset] != "-h" || arg[end_offset + 1] != kHostName) { |
| return false; |
| } |
| end_offset += 2; |
| } |
| |
| if (has_arp_gateway) { |
| if (arg[end_offset] != "-R" || arg[end_offset + 1] != "--unicast") { |
| return false; |
| } |
| end_offset += 2; |
| } |
| |
| std::string device_arg = has_lease_suffix ? std::string(kDeviceName) + "=" + |
| std::string(kLeaseFileSuffix) |
| : kDeviceName; |
| return arg[end_offset] == device_arg; |
| } |
| |
| TEST_F(DHCPControllerTest, StartWithoutLeaseSuffix) { |
| CreateMockMinijailConfig(kHostName, kDeviceName, kArpGateway); |
| EXPECT_CALL( |
| process_manager_, |
| StartProcessInMinijail( |
| _, _, IsDHCPCDArgs(kHasHostname, kArpGateway, !kHasLeaseSuffix), _, _, |
| _)) |
| .WillOnce(Return(-1)); |
| EXPECT_FALSE(StartInstance()); |
| } |
| |
| TEST_F(DHCPControllerTest, StartWithHostname) { |
| CreateMockMinijailConfig(kHostName, kLeaseFileSuffix, kArpGateway); |
| EXPECT_CALL( |
| process_manager_, |
| StartProcessInMinijail( |
| _, _, IsDHCPCDArgs(kHasHostname, kArpGateway, kHasLeaseSuffix), _, _, |
| _)) |
| .WillOnce(Return(-1)); |
| EXPECT_FALSE(StartInstance()); |
| } |
| |
| TEST_F(DHCPControllerTest, StartWithEmptyHostname) { |
| CreateMockMinijailConfig("", kLeaseFileSuffix, kArpGateway); |
| EXPECT_CALL( |
| process_manager_, |
| StartProcessInMinijail( |
| _, _, IsDHCPCDArgs(!kHasHostname, kArpGateway, kHasLeaseSuffix), _, _, |
| _)) |
| .WillOnce(Return(-1)); |
| EXPECT_FALSE(StartInstance()); |
| } |
| |
| TEST_F(DHCPControllerTest, StartWithoutArpGateway) { |
| CreateMockMinijailConfig(kHostName, kLeaseFileSuffix, !kArpGateway); |
| EXPECT_CALL( |
| process_manager_, |
| StartProcessInMinijail( |
| _, _, IsDHCPCDArgs(kHasHostname, !kArpGateway, kHasLeaseSuffix), _, _, |
| _)) |
| .WillOnce(Return(-1)); |
| EXPECT_FALSE(StartInstance()); |
| } |
| |
| TEST_F(DHCPControllerTest, TimeToLeaseExpiry_Success) { |
| IPConfig::Properties properties; |
| properties.lease_duration_seconds = kLeaseDuration; |
| SetCurrentTimeToSecond(kTimeNow); |
| InvokeOnIPConfigUpdated(properties, true); |
| |
| for (uint32_t i = 0; i < kLeaseDuration; i++) { |
| SetCurrentTimeToSecond(kTimeNow + i); |
| EXPECT_EQ(base::Seconds(kLeaseDuration - i), |
| controller_->TimeToLeaseExpiry()); |
| } |
| } |
| |
| TEST_F(DHCPControllerTest, TimeToLeaseExpiry_NoDHCPLease) { |
| SetDHCPVerboseLog(); |
| ScopedMockLog log; |
| // |current_lease_expiration_time_| has not been set, so expect an error. |
| EXPECT_CALL(log, Log(_, _, EndsWith("No current DHCP lease"))); |
| EXPECT_FALSE(controller_->TimeToLeaseExpiry().has_value()); |
| ResetDHCPVerboseLog(); |
| } |
| |
| TEST_F(DHCPControllerTest, TimeToLeaseExpiry_CurrentLeaseExpired) { |
| SetDHCPVerboseLog(); |
| IPConfig::Properties properties; |
| properties.lease_duration_seconds = kLeaseDuration; |
| SetCurrentTimeToSecond(kTimeNow); |
| InvokeOnIPConfigUpdated(properties, true); |
| |
| // Lease should expire at kTimeNow + kLeaseDuration. |
| ScopedMockLog log; |
| SetCurrentTimeToSecond(kTimeNow + kLeaseDuration + 1); |
| EXPECT_CALL(log, |
| Log(_, _, EndsWith("Current DHCP lease has already expired"))); |
| EXPECT_FALSE(controller_->TimeToLeaseExpiry().has_value()); |
| ResetDHCPVerboseLog(); |
| } |
| |
| TEST_F(DHCPControllerTest, ExpiryMetrics) { |
| // Get a lease with duration of 1 second, the expiry callback should be |
| // triggered right after 1 second. |
| IPConfig::Properties properties; |
| properties.lease_duration_seconds = 1; |
| InvokeOnIPConfigUpdated(properties, true); |
| |
| dispatcher()->task_environment().FastForwardBy(base::Milliseconds(500)); |
| |
| EXPECT_CALL(metrics_, SendToUMA(Metrics::kMetricExpiredLeaseLengthSeconds, |
| Technology(Technology::kUnknown), 1)); |
| dispatcher()->task_environment().FastForwardBy(base::Milliseconds(500)); |
| } |
| |
| namespace { |
| |
| class DHCPControllerCallbackTest : public DHCPControllerTest { |
| public: |
| void SetUp() override { |
| DHCPControllerTest::SetUp(); |
| controller_->RegisterCallbacks( |
| base::BindRepeating(&DHCPControllerCallbackTest::UpdateCallback, |
| base::Unretained(this)), |
| base::BindRepeating(&DHCPControllerCallbackTest::FailureCallback, |
| base::Unretained(this))); |
| } |
| |
| MOCK_METHOD(void, UpdateCallback, (const IPConfig::Properties&, bool)); |
| MOCK_METHOD(void, FailureCallback, ()); |
| |
| void ExpectUpdateCallback(bool new_lease_acquired) { |
| EXPECT_CALL(*this, UpdateCallback(_, new_lease_acquired)) |
| .WillOnce(SaveArg<0>(&update_properties_)); |
| EXPECT_CALL(*this, FailureCallback()).Times(0); |
| dispatcher()->task_environment().RunUntilIdle(); |
| } |
| |
| void ExpectFailureCallback() { |
| EXPECT_CALL(*this, UpdateCallback(_, _)).Times(0); |
| EXPECT_CALL(*this, FailureCallback()); |
| dispatcher()->task_environment().RunUntilIdle(); |
| } |
| |
| protected: |
| IPConfig::Properties update_properties_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(DHCPControllerCallbackTest, ProcessEventSignalSuccess) { |
| for (const auto& reason : |
| {DHCPController::kReasonBound, DHCPController::kReasonRebind, |
| DHCPController::kReasonReboot, DHCPController::kReasonRenew}) { |
| int address_octet = 0; |
| for (const auto lease_time_given : {false, true}) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, |
| ++address_octet); |
| if (lease_time_given) { |
| const uint32_t kLeaseTime = 1; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyLeaseTime, |
| kLeaseTime); |
| } |
| controller_->ProcessEventSignal(reason, conf); |
| ExpectUpdateCallback(true); |
| std::string failure_message = std::string(reason) + |
| " failed with lease time " + |
| (lease_time_given ? "given" : "not given"); |
| EXPECT_TRUE(Mock::VerifyAndClearExpectations(this)) << failure_message; |
| EXPECT_EQ(base::StringPrintf("%d.0.0.0", address_octet), |
| update_properties_.address) |
| << failure_message; |
| } |
| } |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, ProcessEventSignalFail) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, 0x01020304); |
| controller_->lease_acquisition_timeout_callback_.Reset(base::DoNothing()); |
| controller_->lease_expiration_callback_.Reset(base::DoNothing()); |
| controller_->ProcessEventSignal(DHCPController::kReasonFail, conf); |
| ExpectFailureCallback(); |
| Mock::VerifyAndClearExpectations(this); |
| EXPECT_TRUE(update_properties_.address.empty()); |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| EXPECT_TRUE(controller_->lease_expiration_callback_.IsCancelled()); |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, ProcessEventSignalUnknown) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, 0x01020304); |
| EXPECT_CALL(*this, UpdateCallback(_, _)).Times(0); |
| EXPECT_CALL(*this, FailureCallback()).Times(0); |
| controller_->ProcessEventSignal("unknown", conf); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, ProcessEventSignalGatewayArp) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, 0x01020304); |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(0)); |
| StartInstance(); |
| controller_->ProcessEventSignal(DHCPController::kReasonGatewayArp, conf); |
| ExpectUpdateCallback(false); |
| Mock::VerifyAndClearExpectations(this); |
| EXPECT_EQ("4.3.2.1", update_properties_.address); |
| // Will not fail on acquisition timeout since Gateway ARP is active. |
| EXPECT_FALSE(ShouldFailOnAcquisitionTimeout()); |
| |
| // An official reply from a DHCP server should reset our GatewayArp state. |
| controller_->ProcessEventSignal(DHCPController::kReasonRenew, conf); |
| ExpectUpdateCallback(true); |
| Mock::VerifyAndClearExpectations(this); |
| // Will fail on acquisition timeout since Gateway ARP is not active. |
| EXPECT_TRUE(ShouldFailOnAcquisitionTimeout()); |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, ProcessEventSignalGatewayArpNak) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, 0x01020304); |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(0)); |
| StartInstance(); |
| controller_->ProcessEventSignal(DHCPController::kReasonGatewayArp, conf); |
| EXPECT_FALSE(ShouldFailOnAcquisitionTimeout()); |
| |
| // Sending a NAK should clear is_gateway_arp_active_. |
| controller_->ProcessEventSignal(DHCPController::kReasonNak, conf); |
| // Will fail on acquisition timeout since Gateway ARP is not active. |
| EXPECT_TRUE(ShouldFailOnAcquisitionTimeout()); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, StoppedDuringFailureCallback) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, 0x01020304); |
| // 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. |
| controller_->ProcessEventSignal(DHCPController::kReasonFail, conf); |
| EXPECT_CALL(*this, FailureCallback()) |
| .WillOnce(InvokeWithoutArgs(this, &DHCPControllerTest::StopInstance)); |
| dispatcher()->task_environment().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClearExpectations(this)); |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| EXPECT_TRUE(controller_->lease_expiration_callback_.IsCancelled()); |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, StoppedDuringSuccessCallback) { |
| KeyValueStore conf; |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyIPAddress, 0x01020304); |
| conf.Set<uint32_t>(DHCPv4Config::kConfigurationKeyLeaseTime, kLeaseDuration); |
| |
| // 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. |
| controller_->ProcessEventSignal(DHCPController::kReasonBound, conf); |
| EXPECT_CALL(*this, UpdateCallback(_, true)) |
| .WillOnce(InvokeWithoutArgs(this, &DHCPControllerTest::StopInstance)); |
| dispatcher()->task_environment().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClearExpectations(this)); |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| EXPECT_TRUE(controller_->lease_expiration_callback_.IsCancelled()); |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, ProcessAcquisitionTimeout) { |
| // Do not fail on acquisition timeout (i.e. ARP gateway is active). |
| SetShouldFailOnAcquisitionTimeout(false); |
| EXPECT_CALL(*this, FailureCallback()).Times(0); |
| controller_->ProcessAcquisitionTimeout(); |
| dispatcher()->task_environment().RunUntilIdle(); |
| Mock::VerifyAndClearExpectations(this); |
| Mock::VerifyAndClearExpectations(controller_.get()); |
| |
| // Fail on acquisition timeout. |
| SetShouldFailOnAcquisitionTimeout(true); |
| controller_->ProcessAcquisitionTimeout(); |
| ExpectFailureCallback(); |
| Mock::VerifyAndClearExpectations(this); |
| Mock::VerifyAndClearExpectations(controller_.get()); |
| } |
| |
| TEST_F(DHCPControllerTest, ReleaseIP) { |
| controller_->pid_ = 1 << 18; // Ensure unknown positive PID. |
| EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1); |
| SetShouldKeepLeaseOnDisconnect(false); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->ReleaseIP(DHCPController::kReleaseReasonDisconnect)); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, KeepLeaseOnDisconnect) { |
| controller_->pid_ = 1 << 18; // Ensure unknown positive PID. |
| |
| // Keep lease on disconnect (i.e. ARP gateway is enabled). |
| SetShouldKeepLeaseOnDisconnect(true); |
| EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->ReleaseIP(DHCPController::kReleaseReasonDisconnect)); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, ReleaseLeaseOnDisconnect) { |
| controller_->pid_ = 1 << 18; // Ensure unknown positive PID. |
| |
| // Release lease on disconnect. |
| SetShouldKeepLeaseOnDisconnect(false); |
| EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->ReleaseIP(DHCPController::kReleaseReasonDisconnect)); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, ReleaseIPStaticIPWithLease) { |
| controller_->pid_ = 1 << 18; // Ensure unknown positive PID. |
| controller_->is_lease_active_ = true; |
| EXPECT_CALL(*proxy_, Release(kDeviceName)); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->ReleaseIP(DHCPController::kReleaseReasonStaticIP)); |
| EXPECT_EQ(nullptr, controller_->proxy_); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, ReleaseIPStaticIPWithoutLease) { |
| controller_->pid_ = 1 << 18; // Ensure unknown positive PID. |
| controller_->is_lease_active_ = false; |
| EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0); |
| MockDHCPProxy* proxy_pointer = proxy_.get(); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->ReleaseIP(DHCPController::kReleaseReasonStaticIP)); |
| // Expect that proxy has not been released. |
| EXPECT_EQ(proxy_pointer, controller_->proxy_.get()); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, RenewIP) { |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(-1)); |
| controller_->pid_ = 0; |
| EXPECT_FALSE( |
| controller_->RenewIP()); // Expect a call to Start() if pid_ is 0. |
| Mock::VerifyAndClearExpectations(&process_manager_); |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .Times(0); |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| controller_->lease_expiration_callback_.Reset(base::DoNothing()); |
| controller_->pid_ = 456; |
| EXPECT_FALSE(controller_->RenewIP()); // Expect no crash with NULL proxy. |
| EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->RenewIP()); |
| EXPECT_FALSE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| EXPECT_TRUE(controller_->lease_expiration_callback_.IsCancelled()); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, RequestIP) { |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| controller_->pid_ = 567; |
| EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->RenewIP()); |
| EXPECT_FALSE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, RequestIPTimeout) { |
| SetShouldFailOnAcquisitionTimeout(true); |
| ExpectFailureCallback(); |
| controller_->lease_acquisition_timeout_ = base::TimeDelta(); |
| controller_->pid_ = 567; |
| EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1); |
| controller_->proxy_ = std::move(proxy_); |
| controller_->RenewIP(); |
| controller_->dispatcher_->DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(this); |
| Mock::VerifyAndClearExpectations(controller_.get()); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, Restart) { |
| const int kPID1 = 1 << 17; // Ensure unknown positive PID. |
| const int kPID2 = 987; |
| controller_->pid_ = kPID1; |
| EXPECT_CALL(provider_, UnbindPID(kPID1)); |
| EXPECT_CALL(process_manager_, StopProcessAndBlock(kPID1)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(kPID2)); |
| EXPECT_CALL(provider_, BindPID(kPID2, IsWeakPtrTo(controller_.get()))); |
| EXPECT_TRUE(controller_->Restart()); |
| EXPECT_EQ(kPID2, controller_->pid_); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerTest, RestartNoClient) { |
| const int kPID = 777; |
| EXPECT_CALL(process_manager_, StopProcessAndBlock(_)).Times(0); |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(kPID)); |
| EXPECT_CALL(provider_, BindPID(kPID, IsWeakPtrTo(controller_.get()))); |
| EXPECT_TRUE(controller_->Restart()); |
| EXPECT_EQ(kPID, controller_->pid_); |
| controller_->pid_ = 0; |
| } |
| |
| TEST_F(DHCPControllerCallbackTest, StartTimeout) { |
| SetShouldFailOnAcquisitionTimeout(true); |
| ExpectFailureCallback(); |
| controller_->lease_acquisition_timeout_ = base::TimeDelta(); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(Return(0)); |
| controller_->Start(); |
| controller_->dispatcher_->DispatchPendingEvents(); |
| Mock::VerifyAndClearExpectations(this); |
| Mock::VerifyAndClearExpectations(controller_.get()); |
| } |
| |
| TEST_F(DHCPControllerTest, 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__)))); |
| controller_->pid_ = kPID; |
| controller_->lease_acquisition_timeout_callback_.Reset(base::DoNothing()); |
| controller_->lease_expiration_callback_.Reset(base::DoNothing()); |
| EXPECT_CALL(provider_, UnbindPID(kPID)); |
| controller_->Stop(__func__); |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| EXPECT_TRUE(controller_->lease_expiration_callback_.IsCancelled()); |
| EXPECT_FALSE(controller_->pid_); |
| } |
| |
| TEST_F(DHCPControllerTest, StopDuringRequestIP) { |
| controller_->pid_ = 567; |
| EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1); |
| controller_->proxy_ = std::move(proxy_); |
| EXPECT_TRUE(controller_->RenewIP()); |
| EXPECT_FALSE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| controller_->pid_ = 0; // Keep Stop from killing a real process. |
| controller_->Stop(__func__); |
| EXPECT_TRUE(controller_->lease_acquisition_timeout_callback_.IsCancelled()); |
| } |
| |
| namespace { |
| // Verifies the existence of pid file and lease file after dhcpcd exited. |
| class DHCPControllerDHCPCDStoppedTest : public DHCPControllerTest { |
| protected: |
| void StartAndSaveExitCallback() { |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _)) |
| .WillOnce(WithArg<5>([this](ProcessManager::ExitCallback cb) { |
| exit_callback_ = std::move(cb); |
| return kPID; |
| })); |
| EXPECT_CALL(provider_, BindPID(kPID, IsWeakPtrTo(controller_.get()))); |
| StartInstance(); |
| } |
| |
| // Creates pid and lease files in a ScopedTempDir which should be generated by |
| // dhcpcd normally. |
| void PrepareFiles() { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| controller_->set_root_for_testing(temp_dir_.GetPath()); |
| base::FilePath varrun = temp_dir_.GetPath().Append("var/run/dhcpcd"); |
| ASSERT_TRUE(base::CreateDirectory(varrun)); |
| pid_file_ = |
| varrun.Append(base::StringPrintf("dhcpcd-%s-4.pid", kDeviceName)); |
| base::FilePath varlib = temp_dir_.GetPath().Append("var/lib/dhcpcd"); |
| ASSERT_TRUE(base::CreateDirectory(varlib)); |
| lease_file_ = |
| varlib.Append(base::StringPrintf("dhcpcd-%s.lease", kDeviceName)); |
| ASSERT_EQ(0, base::WriteFile(pid_file_, "", 0)); |
| ASSERT_EQ(0, base::WriteFile(lease_file_, "", 0)); |
| ASSERT_TRUE(base::PathExists(pid_file_)); |
| ASSERT_TRUE(base::PathExists(lease_file_)); |
| } |
| |
| void StopAndExpect(bool lease_file_exists) { |
| ScopedMockLog log; |
| // We use a non-zero exit status so that we get the log message. |
| EXPECT_CALL(log, Log(_, _, ::testing::EndsWith("status 10"))); |
| EXPECT_CALL(provider_, UnbindPID(kPID)); |
| std::move(exit_callback_).Run(10); |
| |
| EXPECT_FALSE(base::PathExists(pid_file_)); |
| EXPECT_EQ(lease_file_exists, base::PathExists(lease_file_)); |
| } |
| |
| base::FilePath lease_file_; |
| base::FilePath pid_file_; |
| base::ScopedTempDir temp_dir_; |
| ProcessManager::ExitCallback exit_callback_; |
| }; |
| |
| TEST_F(DHCPControllerDHCPCDStoppedTest, StopEphemral) { |
| CreateMockMinijailConfig(kHostName, kDeviceName, kArpGateway); |
| StartAndSaveExitCallback(); |
| PrepareFiles(); |
| StopAndExpect(false); |
| } |
| |
| TEST_F(DHCPControllerDHCPCDStoppedTest, StopPersistent) { |
| CreateMockMinijailConfig(kHostName, kLeaseFileSuffix, kArpGateway); |
| StartAndSaveExitCallback(); |
| PrepareFiles(); |
| StopAndExpect(true); |
| } |
| } // namespace |
| |
| } // namespace shill |