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
41 using base::Bind;
42 using base::FilePath;
43 using base::ScopedTempDir;
44 using base::Unretained;
45 using std::string;
46 using std::unique_ptr;
47 using std::vector;
48 using testing::_;
49 using testing::AnyNumber;
50 using testing::ContainsRegex;
51 using testing::InvokeWithoutArgs;
52 using testing::Mock;
53 using testing::Return;
54 using testing::SetArgumentPointee;
55 using testing::Test;
56
57 namespace shill {
58
59 namespace {
60 const char kDeviceName[] = "eth0";
61 const char kDhcpMethod[] = "dhcp";
62 const char kLeaseFileSuffix[] = "leasefilesuffix";
63 const bool kHasLeaseSuffix = true;
64 } // namespace
65
66 class TestDHCPConfig : public DHCPConfig {
67 public:
TestDHCPConfig(ControlInterface * control_interface,EventDispatcher * dispatcher,DHCPProvider * provider,const std::string & device_name,const std::string & type,const std::string & lease_file_suffix)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
~TestDHCPConfig()81 ~TestDHCPConfig() {}
82
ProcessEventSignal(const std::string & reason,const KeyValueStore & configuration)83 void ProcessEventSignal(const std::string& reason,
84 const KeyValueStore& configuration) override {}
ProcessStatusChangeSignal(const std::string & status)85 void ProcessStatusChangeSignal(const std::string& status) override {}
86
87 MOCK_METHOD0(ShouldFailOnAcquisitionTimeout, bool());
88 MOCK_METHOD0(ShouldKeepLeaseOnDisconnect, bool());
89 };
90
91 typedef scoped_refptr<TestDHCPConfig> TestDHCPConfigRefPtr;
92
93 class DHCPConfigTest : public PropertyStoreTest {
94 public:
DHCPConfigTest()95 DHCPConfigTest()
96 : proxy_(new MockDHCPProxy()),
97 config_(new TestDHCPConfig(&control_,
98 dispatcher(),
99 &provider_,
100 kDeviceName,
101 kDhcpMethod,
102 kLeaseFileSuffix)) {}
103
SetUp()104 virtual void SetUp() {
105 config_->process_manager_ = &process_manager_;
106 }
107
StopInstance()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
124 const int DHCPConfigTest::kPID = 123456;
125
CreateMockMinijailConfig(const string & lease_suffix)126 TestDHCPConfigRefPtr 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
TEST_F(DHCPConfigTest,InitProxy)139 TEST_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
TEST_F(DHCPConfigTest,StartFail)152 TEST_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
160 MATCHER_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
TEST_F(DHCPConfigTest,StartWithoutLeaseSuffix)173 TEST_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
182 namespace {
183
184 class DHCPConfigCallbackTest : public DHCPConfigTest {
185 public:
SetUp()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.
ConfigRef()202 const IPConfigRefPtr& ConfigRef() { return ip_config_; }
203
204 private:
205 IPConfigRefPtr ip_config_;
206 };
207
DoNothing()208 void DoNothing() {}
209
210 } // namespace
211
TEST_F(DHCPConfigCallbackTest,NotifyFailure)212 TEST_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
TEST_F(DHCPConfigCallbackTest,StoppedDuringFailureCallback)224 TEST_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
TEST_F(DHCPConfigCallbackTest,StoppedDuringSuccessCallback)236 TEST_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
TEST_F(DHCPConfigCallbackTest,ProcessAcquisitionTimeout)253 TEST_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
TEST_F(DHCPConfigTest,ReleaseIP)271 TEST_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
TEST_F(DHCPConfigTest,KeepLeaseOnDisconnect)279 TEST_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
TEST_F(DHCPConfigTest,ReleaseLeaseOnDisconnect)291 TEST_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
TEST_F(DHCPConfigTest,ReleaseIPStaticIPWithLease)303 TEST_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
TEST_F(DHCPConfigTest,ReleaseIPStaticIPWithoutLease)313 TEST_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
TEST_F(DHCPConfigTest,RenewIP)325 TEST_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
TEST_F(DHCPConfigTest,RequestIP)345 TEST_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
TEST_F(DHCPConfigCallbackTest,RequestIPTimeout)355 TEST_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
TEST_F(DHCPConfigTest,Restart)371 TEST_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
TEST_F(DHCPConfigTest,RestartNoClient)385 TEST_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
TEST_F(DHCPConfigCallbackTest,StartTimeout)396 TEST_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
TEST_F(DHCPConfigTest,Stop)411 TEST_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
TEST_F(DHCPConfigTest,StopDuringRequestIP)427 TEST_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
TEST_F(DHCPConfigTest,SetProperty)438 TEST_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