| // |
| // Copyright (C) 2015 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/dhcpv6_config.h" |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/strings/stringprintf.h> |
| #if defined(__ANDROID__) |
| #include <dbus/service_constants.h> |
| #else |
| #include <chromeos/dbus/service_constants.h> |
| #endif // __ANDROID__ |
| |
| #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 kLeaseFileSuffix[] = "leasefilesuffix"; |
| const bool kHasLeaseSuffix = true; |
| const char kIPAddress[] = "2001:db8:0:1::1"; |
| const char kDelegatedPrefix[] = "2001:db8:0:100::"; |
| } // namespace |
| |
| typedef scoped_refptr<DHCPv6Config> DHCPv6ConfigRefPtr; |
| |
| class DHCPv6ConfigTest : public PropertyStoreTest { |
| public: |
| DHCPv6ConfigTest() |
| : proxy_(new MockDHCPProxy()), |
| config_(new DHCPv6Config(&control_, |
| dispatcher(), |
| &provider_, |
| kDeviceName, |
| kLeaseFileSuffix)) {} |
| |
| virtual void SetUp() { |
| config_->process_manager_ = &process_manager_; |
| } |
| |
| bool StartInstance(DHCPv6ConfigRefPtr config) { |
| return config->Start(); |
| } |
| |
| void StopInstance() { |
| config_->Stop("In test"); |
| } |
| |
| DHCPv6ConfigRefPtr CreateMockMinijailConfig(const string& lease_suffix); |
| DHCPv6ConfigRefPtr CreateRunningConfig(const string& lease_suffix); |
| void StopRunningConfigAndExpect(DHCPv6ConfigRefPtr config, |
| bool lease_file_exists); |
| |
| protected: |
| static const int kPID; |
| static const unsigned int kTag; |
| |
| FilePath lease_file_; |
| FilePath pid_file_; |
| ScopedTempDir temp_dir_; |
| unique_ptr<MockDHCPProxy> proxy_; |
| MockControl control_; |
| MockProcessManager process_manager_; |
| MockDHCPProvider provider_; |
| DHCPv6ConfigRefPtr config_; |
| }; |
| |
| const int DHCPv6ConfigTest::kPID = 123456; |
| const unsigned int DHCPv6ConfigTest::kTag = 77; |
| |
| DHCPv6ConfigRefPtr DHCPv6ConfigTest::CreateMockMinijailConfig( |
| const string& lease_suffix) { |
| DHCPv6ConfigRefPtr config(new DHCPv6Config(&control_, |
| dispatcher(), |
| &provider_, |
| kDeviceName, |
| lease_suffix)); |
| config->process_manager_ = &process_manager_; |
| |
| return config; |
| } |
| |
| DHCPv6ConfigRefPtr DHCPv6ConfigTest::CreateRunningConfig( |
| const string& lease_suffix) { |
| DHCPv6ConfigRefPtr config(new DHCPv6Config(&control_, |
| dispatcher(), |
| &provider_, |
| kDeviceName, |
| lease_suffix)); |
| config->process_manager_ = &process_manager_; |
| EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _)) |
| .WillOnce(Return(kPID)); |
| EXPECT_CALL(provider_, BindPID(kPID, IsRefPtrTo(config))); |
| EXPECT_TRUE(config->Start()); |
| EXPECT_EQ(kPID, config->pid_); |
| |
| EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| config->root_ = temp_dir_.path(); |
| FilePath varrun = temp_dir_.path().Append("var/run/dhcpcd"); |
| EXPECT_TRUE(base::CreateDirectory(varrun)); |
| pid_file_ = varrun.Append(base::StringPrintf("dhcpcd-%s-6.pid", kDeviceName)); |
| FilePath varlib = temp_dir_.path().Append("var/lib/dhcpcd"); |
| EXPECT_TRUE(base::CreateDirectory(varlib)); |
| lease_file_ = |
| varlib.Append(base::StringPrintf("dhcpcd-%s.lease6", kDeviceName)); |
| EXPECT_EQ(0, base::WriteFile(pid_file_, "", 0)); |
| EXPECT_EQ(0, base::WriteFile(lease_file_, "", 0)); |
| EXPECT_TRUE(base::PathExists(pid_file_)); |
| EXPECT_TRUE(base::PathExists(lease_file_)); |
| |
| return config; |
| } |
| |
| void DHCPv6ConfigTest::StopRunningConfigAndExpect(DHCPv6ConfigRefPtr config, |
| 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)); |
| config->OnProcessExited(10); |
| |
| EXPECT_FALSE(base::PathExists(pid_file_)); |
| EXPECT_EQ(lease_file_exists, base::PathExists(lease_file_)); |
| } |
| |
| TEST_F(DHCPv6ConfigTest, ParseConfiguration) { |
| const char kConfigIPAddress[] = "2001:db8:0:1::129"; |
| const char kConfigDelegatedPrefix[] = "2001:db8:1:100::"; |
| const char kConfigNameServer[] = "fec8:0::1"; |
| const char kConfigDomainSearch[] = "example.domain"; |
| const uint32_t kConfigDelegatedPrefixLength = 56; |
| const uint32_t kConfigIPAddressLeaseTime = 5; |
| const uint32_t kConfigDelegatedPrefixLeaseTime = 10; |
| |
| KeyValueStore conf; |
| conf.SetString(DHCPv6Config::kConfigurationKeyIPAddress, kConfigIPAddress); |
| conf.SetUint(DHCPv6Config::kConfigurationKeyIPAddressLeaseTime, |
| kConfigIPAddressLeaseTime); |
| conf.SetString(DHCPv6Config::kConfigurationKeyDelegatedPrefix, |
| kConfigDelegatedPrefix); |
| conf.SetUint(DHCPv6Config::kConfigurationKeyDelegatedPrefixLength, |
| kConfigDelegatedPrefixLength); |
| conf.SetUint(DHCPv6Config::kConfigurationKeyDelegatedPrefixLeaseTime, |
| kConfigDelegatedPrefixLeaseTime); |
| { |
| vector<string> dns; |
| dns.push_back(kConfigNameServer); |
| conf.SetStrings(DHCPv6Config::kConfigurationKeyDNS, dns); |
| } |
| { |
| vector<string> domain_search; |
| domain_search.push_back(kConfigDomainSearch); |
| conf.SetStrings(DHCPv6Config::kConfigurationKeyDomainSearch, domain_search); |
| } |
| conf.SetString("UnknownKey", "UnknownValue"); |
| |
| ASSERT_TRUE(config_->ParseConfiguration(conf)); |
| EXPECT_EQ(kConfigIPAddress, config_->properties_.address); |
| EXPECT_EQ(kConfigDelegatedPrefix, config_->properties_.delegated_prefix); |
| EXPECT_EQ(kConfigDelegatedPrefixLength, |
| config_->properties_.delegated_prefix_length); |
| ASSERT_EQ(1, config_->properties_.dns_servers.size()); |
| EXPECT_EQ(kConfigNameServer, config_->properties_.dns_servers[0]); |
| ASSERT_EQ(1, config_->properties_.domain_search.size()); |
| EXPECT_EQ(kConfigDomainSearch, config_->properties_.domain_search[0]); |
| // Use IP address lease time since it is shorter. |
| EXPECT_EQ(kConfigIPAddressLeaseTime, |
| config_->properties_.lease_duration_seconds); |
| } |
| |
| MATCHER_P(IsDHCPCDv6Args, has_lease_suffix, "") { |
| if (arg[0] != "-B" || |
| arg[1] != "-q" || |
| arg[2] != "-6" || |
| arg[3] != "-a") { |
| return false; |
| } |
| |
| int end_offset = 4; |
| |
| string device_arg = has_lease_suffix ? |
| string(kDeviceName) + "=" + string(kLeaseFileSuffix) : kDeviceName; |
| return arg[end_offset] == device_arg; |
| } |
| |
| TEST_F(DHCPv6ConfigTest, StartDhcpcd) { |
| EXPECT_CALL(process_manager_, |
| StartProcessInMinijail(_, _, IsDHCPCDv6Args(kHasLeaseSuffix), |
| _, _, _, _)) |
| .WillOnce(Return(-1)); |
| EXPECT_FALSE(StartInstance(config_)); |
| } |
| |
| |
| namespace { |
| |
| class DHCPv6ConfigCallbackTest : public DHCPv6ConfigTest { |
| public: |
| virtual void SetUp() { |
| DHCPv6ConfigTest::SetUp(); |
| config_->RegisterUpdateCallback( |
| Bind(&DHCPv6ConfigCallbackTest::SuccessCallback, Unretained(this))); |
| config_->RegisterFailureCallback( |
| Bind(&DHCPv6ConfigCallbackTest::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_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(DHCPv6ConfigCallbackTest, ProcessEventSignalFail) { |
| KeyValueStore conf; |
| conf.SetString(DHCPv6Config::kConfigurationKeyIPAddress, kIPAddress); |
| conf.SetString(DHCPv6Config::kConfigurationKeyDelegatedPrefix, |
| kDelegatedPrefix); |
| EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0); |
| EXPECT_CALL(*this, FailureCallback(ConfigRef())); |
| config_->ProcessEventSignal(DHCPv6Config::kReasonFail, conf); |
| Mock::VerifyAndClearExpectations(this); |
| EXPECT_TRUE(config_->properties().address.empty()); |
| } |
| |
| TEST_F(DHCPv6ConfigCallbackTest, ProcessEventSignalSuccess) { |
| for (const auto& reason : { DHCPv6Config::kReasonBound, |
| DHCPv6Config::kReasonRebind, |
| DHCPv6Config::kReasonReboot, |
| DHCPv6Config::kReasonRenew }) { |
| for (const auto lease_time_given : { false, true }) { |
| KeyValueStore conf; |
| conf.SetString(DHCPv6Config::kConfigurationKeyIPAddress, kIPAddress); |
| conf.SetString(DHCPv6Config::kConfigurationKeyDelegatedPrefix, |
| kDelegatedPrefix); |
| if (lease_time_given) { |
| const uint32_t kLeaseTime = 1; |
| conf.SetUint(DHCPv6Config::kConfigurationKeyIPAddressLeaseTime, |
| kLeaseTime); |
| } |
| EXPECT_CALL(*this, SuccessCallback(ConfigRef(), true)); |
| EXPECT_CALL(*this, FailureCallback(_)).Times(0); |
| config_->ProcessEventSignal(reason, conf); |
| string failure_message = string(reason) + " failed with lease time " + |
| (lease_time_given ? "given" : "not given"); |
| EXPECT_TRUE(Mock::VerifyAndClearExpectations(this)) << failure_message; |
| EXPECT_EQ("2001:db8:0:1::1", config_->properties().address) |
| << failure_message; |
| } |
| } |
| } |
| |
| TEST_F(DHCPv6ConfigCallbackTest, StoppedDuringFailureCallback) { |
| KeyValueStore conf; |
| conf.SetString(DHCPv6Config::kConfigurationKeyIPAddress, kIPAddress); |
| conf.SetString(DHCPv6Config::kConfigurationKeyDelegatedPrefix, |
| kDelegatedPrefix); |
| // 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, &DHCPv6ConfigTest::StopInstance)); |
| config_->ProcessEventSignal(DHCPv6Config::kReasonFail, conf); |
| EXPECT_TRUE(Mock::VerifyAndClearExpectations(this)); |
| } |
| |
| TEST_F(DHCPv6ConfigCallbackTest, StoppedDuringSuccessCallback) { |
| KeyValueStore conf; |
| conf.SetString(DHCPv6Config::kConfigurationKeyIPAddress, kIPAddress); |
| conf.SetString(DHCPv6Config::kConfigurationKeyDelegatedPrefix, |
| kDelegatedPrefix); |
| const uint32_t kLeaseTime = 1; |
| conf.SetUint(DHCPv6Config::kConfigurationKeyIPAddressLeaseTime, kLeaseTime); |
| // 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, &DHCPv6ConfigTest::StopInstance)); |
| config_->ProcessEventSignal(DHCPv6Config::kReasonBound, conf); |
| EXPECT_TRUE(Mock::VerifyAndClearExpectations(this)); |
| } |
| |
| TEST_F(DHCPv6ConfigCallbackTest, ProcessEventSignalUnknown) { |
| KeyValueStore conf; |
| conf.SetString(DHCPv6Config::kConfigurationKeyIPAddress, kIPAddress); |
| conf.SetString(DHCPv6Config::kConfigurationKeyDelegatedPrefix, |
| kDelegatedPrefix); |
| static const char kReasonUnknown[] = "UNKNOWN_REASON"; |
| EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0); |
| EXPECT_CALL(*this, FailureCallback(_)).Times(0); |
| config_->ProcessEventSignal(kReasonUnknown, conf); |
| Mock::VerifyAndClearExpectations(this); |
| EXPECT_TRUE(config_->properties().address.empty()); |
| } |
| |
| TEST_F(DHCPv6ConfigTest, StartSuccessEphemeral) { |
| DHCPv6ConfigRefPtr config = |
| CreateRunningConfig(kDeviceName); |
| StopRunningConfigAndExpect(config, false); |
| } |
| |
| TEST_F(DHCPv6ConfigTest, StartSuccessPersistent) { |
| DHCPv6ConfigRefPtr config = |
| CreateRunningConfig(kLeaseFileSuffix); |
| StopRunningConfigAndExpect(config, true); |
| } |
| |
| } // namespace shill |