1//
2// Copyright (C) 2012 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "shill/dhcp/dhcp_config.h"
18
19#include <memory>
20#include <string>
21#include <vector>
22
23#include <base/bind.h>
24#include <base/strings/stringprintf.h>
25#if defined(__ANDROID__)
26#include <dbus/service_constants.h>
27#else
28#include <chromeos/dbus/service_constants.h>
29#endif  // __ANDROID__
30
31#include "shill/dhcp/mock_dhcp_provider.h"
32#include "shill/dhcp/mock_dhcp_proxy.h"
33#include "shill/event_dispatcher.h"
34#include "shill/mock_control.h"
35#include "shill/mock_log.h"
36#include "shill/mock_metrics.h"
37#include "shill/mock_process_manager.h"
38#include "shill/property_store_unittest.h"
39#include "shill/testing.h"
40
41using base::Bind;
42using base::FilePath;
43using base::ScopedTempDir;
44using base::Unretained;
45using std::string;
46using std::unique_ptr;
47using std::vector;
48using testing::_;
49using testing::AnyNumber;
50using testing::ContainsRegex;
51using testing::InvokeWithoutArgs;
52using testing::Mock;
53using testing::Return;
54using testing::SetArgumentPointee;
55using testing::Test;
56
57namespace shill {
58
59namespace {
60const char kDeviceName[] = "eth0";
61const char kDhcpMethod[] = "dhcp";
62const char kLeaseFileSuffix[] = "leasefilesuffix";
63const bool kHasLeaseSuffix = true;
64}  // namespace
65
66class TestDHCPConfig : public DHCPConfig {
67 public:
68  TestDHCPConfig(ControlInterface* control_interface,
69                 EventDispatcher* dispatcher,
70                 DHCPProvider* provider,
71                 const std::string& device_name,
72                 const std::string& type,
73                 const std::string& lease_file_suffix)
74     : DHCPConfig(control_interface,
75                  dispatcher,
76                  provider,
77                  device_name,
78                  type,
79                  lease_file_suffix) {}
80
81  ~TestDHCPConfig() {}
82
83  void ProcessEventSignal(const std::string& reason,
84                          const KeyValueStore& configuration) override {}
85  void ProcessStatusChangeSignal(const std::string& status) override {}
86
87  MOCK_METHOD0(ShouldFailOnAcquisitionTimeout, bool());
88  MOCK_METHOD0(ShouldKeepLeaseOnDisconnect, bool());
89};
90
91typedef scoped_refptr<TestDHCPConfig> TestDHCPConfigRefPtr;
92
93class DHCPConfigTest : public PropertyStoreTest {
94 public:
95  DHCPConfigTest()
96      : proxy_(new MockDHCPProxy()),
97        config_(new TestDHCPConfig(&control_,
98                                   dispatcher(),
99                                   &provider_,
100                                   kDeviceName,
101                                   kDhcpMethod,
102                                   kLeaseFileSuffix)) {}
103
104  virtual void SetUp() {
105    config_->process_manager_ = &process_manager_;
106  }
107
108  void StopInstance() {
109    config_->Stop("In test");
110  }
111
112  TestDHCPConfigRefPtr CreateMockMinijailConfig(const string& lease_suffix);
113
114 protected:
115  static const int kPID;
116
117  unique_ptr<MockDHCPProxy> proxy_;
118  MockControl control_;
119  MockProcessManager process_manager_;
120  TestDHCPConfigRefPtr config_;
121  MockDHCPProvider provider_;
122};
123
124const int DHCPConfigTest::kPID = 123456;
125
126TestDHCPConfigRefPtr DHCPConfigTest::CreateMockMinijailConfig(
127    const string& lease_suffix) {
128  TestDHCPConfigRefPtr config(new TestDHCPConfig(&control_,
129                                                 dispatcher(),
130                                                 &provider_,
131                                                 kDeviceName,
132                                                 kDhcpMethod,
133                                                 lease_suffix));
134  config->process_manager_ = &process_manager_;
135
136  return config;
137}
138
139TEST_F(DHCPConfigTest, InitProxy) {
140  static const char kService[] = ":1.200";
141  EXPECT_TRUE(proxy_.get());
142  EXPECT_FALSE(config_->proxy_.get());
143  EXPECT_CALL(control_, CreateDHCPProxy(kService))
144      .WillOnce(ReturnAndReleasePointee(&proxy_));
145  config_->InitProxy(kService);
146  EXPECT_FALSE(proxy_.get());
147  EXPECT_TRUE(config_->proxy_.get());
148
149  config_->InitProxy(kService);
150}
151
152TEST_F(DHCPConfigTest, StartFail) {
153  EXPECT_CALL(process_manager_,
154              StartProcessInMinijail(_, _, _, _, _, _, _))
155      .WillOnce(Return(-1));
156  EXPECT_FALSE(config_->Start());
157  EXPECT_EQ(0, config_->pid_);
158}
159
160MATCHER_P(IsDHCPCDArgs, has_lease_suffix, "") {
161  if (arg[0] != "-B" ||
162      arg[1] != "-q") {
163    return false;
164  }
165
166  int end_offset = 2;
167
168  string device_arg = has_lease_suffix ?
169      string(kDeviceName) + "=" + string(kLeaseFileSuffix) : kDeviceName;
170  return arg[end_offset] == device_arg;
171}
172
173TEST_F(DHCPConfigTest, StartWithoutLeaseSuffix) {
174  TestDHCPConfigRefPtr config = CreateMockMinijailConfig(kDeviceName);
175  EXPECT_CALL(process_manager_,
176              StartProcessInMinijail(_, _, IsDHCPCDArgs(!kHasLeaseSuffix),
177                                     _, _, _, _))
178      .WillOnce(Return(-1));
179  EXPECT_FALSE(config->Start());
180}
181
182namespace {
183
184class DHCPConfigCallbackTest : public DHCPConfigTest {
185 public:
186  virtual void SetUp() {
187    DHCPConfigTest::SetUp();
188    config_->RegisterUpdateCallback(
189        Bind(&DHCPConfigCallbackTest::SuccessCallback, Unretained(this)));
190    config_->RegisterFailureCallback(
191        Bind(&DHCPConfigCallbackTest::FailureCallback, Unretained(this)));
192    ip_config_ = config_;
193  }
194
195  MOCK_METHOD2(SuccessCallback,
196               void(const IPConfigRefPtr& ipconfig, bool new_lease_acquired));
197  MOCK_METHOD1(FailureCallback, void(const IPConfigRefPtr& ipconfig));
198
199  // The mock methods above take IPConfigRefPtr because this is the type
200  // that the registered callbacks take.  This conversion of the DHCP
201  // config ref pointer eases our work in setting up expectations.
202  const IPConfigRefPtr& ConfigRef() { return ip_config_; }
203
204 private:
205  IPConfigRefPtr ip_config_;
206};
207
208void DoNothing() {}
209
210}  // namespace
211
212TEST_F(DHCPConfigCallbackTest, NotifyFailure) {
213  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
214  EXPECT_CALL(*this, FailureCallback(ConfigRef()));
215  config_->lease_acquisition_timeout_callback_.Reset(base::Bind(&DoNothing));
216  config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
217  config_->NotifyFailure();
218  Mock::VerifyAndClearExpectations(this);
219  EXPECT_TRUE(config_->properties().address.empty());
220  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
221  EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
222}
223
224TEST_F(DHCPConfigCallbackTest, StoppedDuringFailureCallback) {
225  // Stop the DHCP config while it is calling the failure callback.  We
226  // need to ensure that no callbacks are left running inadvertently as
227  // a result.
228  EXPECT_CALL(*this, FailureCallback(ConfigRef()))
229      .WillOnce(InvokeWithoutArgs(this, &DHCPConfigTest::StopInstance));
230  config_->NotifyFailure();
231  EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
232  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
233  EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
234}
235
236TEST_F(DHCPConfigCallbackTest, StoppedDuringSuccessCallback) {
237  IPConfig::Properties properties;
238  properties.address = "1.2.3.4";
239  properties.lease_duration_seconds = 1;
240  // Stop the DHCP config while it is calling the success callback.  This
241  // can happen if the device has a static IP configuration and releases
242  // the lease after accepting other network parameters from the DHCP
243  // IPConfig properties.  We need to ensure that no callbacks are left
244  // running inadvertently as a result.
245  EXPECT_CALL(*this, SuccessCallback(ConfigRef(), true))
246      .WillOnce(InvokeWithoutArgs(this, &DHCPConfigTest::StopInstance));
247  config_->UpdateProperties(properties, true);
248  EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
249  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
250  EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
251}
252
253TEST_F(DHCPConfigCallbackTest, ProcessAcquisitionTimeout) {
254  // Do not fail on acquisition timeout (e.g. ARP gateway is active).
255  EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
256      .WillOnce(Return(false));
257  EXPECT_CALL(*this, FailureCallback(_)).Times(0);
258  config_->ProcessAcquisitionTimeout();
259  Mock::VerifyAndClearExpectations(this);
260  Mock::VerifyAndClearExpectations(config_.get());
261
262  // Fail on acquisition timeout.
263  EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
264      .WillOnce(Return(true));
265  EXPECT_CALL(*this, FailureCallback(_)).Times(1);
266  config_->ProcessAcquisitionTimeout();
267  Mock::VerifyAndClearExpectations(this);
268  Mock::VerifyAndClearExpectations(config_.get());
269}
270
271TEST_F(DHCPConfigTest, ReleaseIP) {
272  config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
273  EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1);
274  config_->proxy_.reset(proxy_.release());
275  EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonDisconnect));
276  config_->pid_ = 0;
277}
278
279TEST_F(DHCPConfigTest, KeepLeaseOnDisconnect) {
280  config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
281
282  // Keep lease on disconnect (e.g. ARP gateway is enabled).
283  EXPECT_CALL(*config_.get(), ShouldKeepLeaseOnDisconnect())
284      .WillOnce(Return(true));
285  EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0);
286  config_->proxy_.reset(proxy_.release());
287  EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonDisconnect));
288  config_->pid_ = 0;
289}
290
291TEST_F(DHCPConfigTest, ReleaseLeaseOnDisconnect) {
292  config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
293
294  // Release lease on disconnect.
295  EXPECT_CALL(*config_.get(), ShouldKeepLeaseOnDisconnect())
296      .WillOnce(Return(false));
297  EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1);
298  config_->proxy_.reset(proxy_.release());
299  EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonDisconnect));
300  config_->pid_ = 0;
301}
302
303TEST_F(DHCPConfigTest, ReleaseIPStaticIPWithLease) {
304  config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
305  config_->is_lease_active_ = true;
306  EXPECT_CALL(*proxy_, Release(kDeviceName));
307  config_->proxy_.reset(proxy_.release());
308  EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonStaticIP));
309  EXPECT_EQ(nullptr, config_->proxy_.get());
310  config_->pid_ = 0;
311}
312
313TEST_F(DHCPConfigTest, ReleaseIPStaticIPWithoutLease) {
314  config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
315  config_->is_lease_active_ = false;
316  EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0);
317  MockDHCPProxy* proxy_pointer = proxy_.get();
318  config_->proxy_.reset(proxy_.release());
319  EXPECT_TRUE(config_->ReleaseIP(IPConfig::kReleaseReasonStaticIP));
320  // Expect that proxy has not been released.
321  EXPECT_EQ(proxy_pointer, config_->proxy_.get());
322  config_->pid_ = 0;
323}
324
325TEST_F(DHCPConfigTest, RenewIP) {
326  EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _))
327      .WillOnce(Return(-1));
328  config_->pid_ = 0;
329  EXPECT_FALSE(config_->RenewIP());  // Expect a call to Start() if pid_ is 0.
330  Mock::VerifyAndClearExpectations(&process_manager_);
331  EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _))
332      .Times(0);
333  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
334  config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
335  config_->pid_ = 456;
336  EXPECT_FALSE(config_->RenewIP());  // Expect no crash with NULL proxy.
337  EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
338  config_->proxy_.reset(proxy_.release());
339  EXPECT_TRUE(config_->RenewIP());
340  EXPECT_FALSE(config_->lease_acquisition_timeout_callback_.IsCancelled());
341  EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
342  config_->pid_ = 0;
343}
344
345TEST_F(DHCPConfigTest, RequestIP) {
346  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
347  config_->pid_ = 567;
348  EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
349  config_->proxy_.reset(proxy_.release());
350  EXPECT_TRUE(config_->RenewIP());
351  EXPECT_FALSE(config_->lease_acquisition_timeout_callback_.IsCancelled());
352  config_->pid_ = 0;
353}
354
355TEST_F(DHCPConfigCallbackTest, RequestIPTimeout) {
356  EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
357      .WillOnce(Return(true));
358  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
359  EXPECT_CALL(*this, FailureCallback(ConfigRef()));
360  config_->lease_acquisition_timeout_seconds_ = 0;
361  config_->pid_ = 567;
362  EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
363  config_->proxy_.reset(proxy_.release());
364  config_->RenewIP();
365  config_->dispatcher_->DispatchPendingEvents();
366  Mock::VerifyAndClearExpectations(this);
367  Mock::VerifyAndClearExpectations(config_.get());
368  config_->pid_ = 0;
369}
370
371TEST_F(DHCPConfigTest, Restart) {
372  const int kPID1 = 1 << 17;  // Ensure unknown positive PID.
373  const int kPID2 = 987;
374  config_->pid_ = kPID1;
375  EXPECT_CALL(provider_, UnbindPID(kPID1));
376  EXPECT_CALL(process_manager_, StopProcessAndBlock(kPID1)).WillOnce(Return(true));
377  EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _))
378      .WillOnce(Return(kPID2));
379  EXPECT_CALL(provider_, BindPID(kPID2, IsRefPtrTo(config_)));
380  EXPECT_TRUE(config_->Restart());
381  EXPECT_EQ(kPID2, config_->pid_);
382  config_->pid_ = 0;
383}
384
385TEST_F(DHCPConfigTest, RestartNoClient) {
386  const int kPID = 777;
387  EXPECT_CALL(process_manager_, StopProcessAndBlock(_)).Times(0);
388  EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _))
389      .WillOnce(Return(kPID));
390  EXPECT_CALL(provider_, BindPID(kPID, IsRefPtrTo(config_)));
391  EXPECT_TRUE(config_->Restart());
392  EXPECT_EQ(kPID, config_->pid_);
393  config_->pid_ = 0;
394}
395
396TEST_F(DHCPConfigCallbackTest, StartTimeout) {
397  EXPECT_CALL(*config_.get(), ShouldFailOnAcquisitionTimeout())
398      .WillOnce(Return(true));
399  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
400  EXPECT_CALL(*this, FailureCallback(ConfigRef()));
401  config_->lease_acquisition_timeout_seconds_ = 0;
402  config_->proxy_.reset(proxy_.release());
403  EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _, _))
404      .WillOnce(Return(0));
405  config_->Start();
406  config_->dispatcher_->DispatchPendingEvents();
407  Mock::VerifyAndClearExpectations(this);
408  Mock::VerifyAndClearExpectations(config_.get());
409}
410
411TEST_F(DHCPConfigTest, Stop) {
412  const int kPID = 1 << 17;  // Ensure unknown positive PID.
413  ScopedMockLog log;
414  EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
415  EXPECT_CALL(log, Log(_, _, ContainsRegex(
416      base::StringPrintf("Stopping.+%s", __func__))));
417  config_->pid_ = kPID;
418  config_->lease_acquisition_timeout_callback_.Reset(base::Bind(&DoNothing));
419  config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
420  EXPECT_CALL(provider_, UnbindPID(kPID));
421  config_->Stop(__func__);
422  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
423  EXPECT_TRUE(config_->lease_expiration_callback_.IsCancelled());
424  EXPECT_FALSE(config_->pid_);
425}
426
427TEST_F(DHCPConfigTest, StopDuringRequestIP) {
428  config_->pid_ = 567;
429  EXPECT_CALL(*proxy_, Rebind(kDeviceName)).Times(1);
430  config_->proxy_.reset(proxy_.release());
431  EXPECT_TRUE(config_->RenewIP());
432  EXPECT_FALSE(config_->lease_acquisition_timeout_callback_.IsCancelled());
433  config_->pid_ = 0;  // Keep Stop from killing a real process.
434  config_->Stop(__func__);
435  EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
436}
437
438TEST_F(DHCPConfigTest, SetProperty) {
439  Error error;
440  // Ensure that an attempt to write a R/O property returns InvalidArgs error.
441  EXPECT_FALSE(config_->mutable_store()->SetAnyProperty(
442      kAddressProperty, PropertyStoreTest::kStringV, &error));
443  EXPECT_TRUE(error.IsFailure());
444  EXPECT_EQ(Error::kInvalidArguments, error.type());
445}
446
447}  // namespace shill
448