blob: 91e26983d154cb4b0c1c01586820fbe874cfd032 [file] [log] [blame]
Darin Petkov7476a262012-04-12 16:30:46 +02001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "shill/l2tp_ipsec_driver.h"
6
Darin Petkovf7ef50a2012-04-16 20:54:31 +02007#include <base/file_util.h>
8#include <base/scoped_temp_dir.h>
Darin Petkov209e6292012-04-20 11:33:32 +02009#include <base/string_util.h>
Darin Petkov7476a262012-04-12 16:30:46 +020010#include <gtest/gtest.h>
11
12#include "shill/event_dispatcher.h"
13#include "shill/nice_mock_control.h"
Darin Petkov209e6292012-04-20 11:33:32 +020014#include "shill/mock_adaptors.h"
Darin Petkov0e9735d2012-04-24 12:33:45 +020015#include "shill/mock_connection.h"
16#include "shill/mock_device_info.h"
Darin Petkov7476a262012-04-12 16:30:46 +020017#include "shill/mock_glib.h"
18#include "shill/mock_manager.h"
19#include "shill/mock_metrics.h"
Darin Petkovf7ef50a2012-04-16 20:54:31 +020020#include "shill/mock_nss.h"
Darin Petkov0e9735d2012-04-24 12:33:45 +020021#include "shill/mock_service.h"
Darin Petkov7476a262012-04-12 16:30:46 +020022#include "shill/mock_vpn_service.h"
23
Darin Petkovf7ef50a2012-04-16 20:54:31 +020024using std::find;
Darin Petkov209e6292012-04-20 11:33:32 +020025using std::map;
Darin Petkovf7ef50a2012-04-16 20:54:31 +020026using std::string;
27using std::vector;
28using testing::_;
29using testing::ElementsAreArray;
Darin Petkov0e9735d2012-04-24 12:33:45 +020030using testing::NiceMock;
Darin Petkovf7ef50a2012-04-16 20:54:31 +020031using testing::Return;
32using testing::ReturnRef;
Darin Petkov209e6292012-04-20 11:33:32 +020033using testing::SetArgumentPointee;
Darin Petkov0e9735d2012-04-24 12:33:45 +020034using testing::StrictMock;
Darin Petkovf7ef50a2012-04-16 20:54:31 +020035
Darin Petkov7476a262012-04-12 16:30:46 +020036namespace shill {
37
Darin Petkov209e6292012-04-20 11:33:32 +020038class L2TPIPSecDriverTest : public testing::Test,
39 public RPCTaskDelegate {
Darin Petkov7476a262012-04-12 16:30:46 +020040 public:
41 L2TPIPSecDriverTest()
Darin Petkov0e9735d2012-04-24 12:33:45 +020042 : device_info_(&control_, &dispatcher_, &metrics_, &manager_),
43 manager_(&control_, &dispatcher_, &metrics_, &glib_),
Darin Petkov209e6292012-04-20 11:33:32 +020044 driver_(new L2TPIPSecDriver(&control_, &manager_, &glib_)),
Darin Petkov7476a262012-04-12 16:30:46 +020045 service_(new MockVPNService(&control_, &dispatcher_, &metrics_,
Darin Petkovf7ef50a2012-04-16 20:54:31 +020046 &manager_, driver_)) {
47 driver_->nss_ = &nss_;
48 }
Darin Petkov7476a262012-04-12 16:30:46 +020049
50 virtual ~L2TPIPSecDriverTest() {}
51
Darin Petkovf7ef50a2012-04-16 20:54:31 +020052 virtual void SetUp() {
53 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
54 }
55
56 virtual void TearDown() {
Darin Petkov209e6292012-04-20 11:33:32 +020057 driver_->child_watch_tag_ = 0;
58 driver_->pid_ = 0;
59 driver_->service_ = NULL;
Darin Petkovf7ef50a2012-04-16 20:54:31 +020060 ASSERT_TRUE(temp_dir_.Delete());
61 }
62
Darin Petkov7476a262012-04-12 16:30:46 +020063 protected:
Darin Petkovf7ef50a2012-04-16 20:54:31 +020064 void SetArg(const string &arg, const string &value) {
65 driver_->args_.SetString(arg, value);
66 }
67
Darin Petkovd4325392012-04-23 15:48:22 +020068 KeyValueStore *GetArgs() {
69 return driver_->args();
70 }
71
Darin Petkovf7ef50a2012-04-16 20:54:31 +020072 // Used to assert that a flag appears in the options.
73 void ExpectInFlags(const vector<string> &options, const string &flag,
74 const string &value);
75
Darin Petkov0e9735d2012-04-24 12:33:45 +020076 FilePath SetupPSKFile();
77
Darin Petkov209e6292012-04-20 11:33:32 +020078 // Inherited from RPCTaskDelegate.
79 virtual void GetLogin(string *user, string *password);
80 virtual void Notify(const string &reason, const map<string, string> &dict);
81
Darin Petkovf7ef50a2012-04-16 20:54:31 +020082 ScopedTempDir temp_dir_;
Darin Petkov7476a262012-04-12 16:30:46 +020083 NiceMockControl control_;
Darin Petkov0e9735d2012-04-24 12:33:45 +020084 NiceMock<MockDeviceInfo> device_info_;
Darin Petkov7476a262012-04-12 16:30:46 +020085 EventDispatcher dispatcher_;
86 MockMetrics metrics_;
87 MockGLib glib_;
88 MockManager manager_;
89 L2TPIPSecDriver *driver_; // Owned by |service_|.
90 scoped_refptr<MockVPNService> service_;
Darin Petkovf7ef50a2012-04-16 20:54:31 +020091 MockNSS nss_;
Darin Petkov7476a262012-04-12 16:30:46 +020092};
93
Darin Petkov209e6292012-04-20 11:33:32 +020094void L2TPIPSecDriverTest::GetLogin(string */*user*/, string */*password*/) {}
95
96void L2TPIPSecDriverTest::Notify(
97 const string &/*reason*/, const map<string, string> &/*dict*/) {}
98
Darin Petkovf7ef50a2012-04-16 20:54:31 +020099void L2TPIPSecDriverTest::ExpectInFlags(
100 const vector<string> &options, const string &flag, const string &value) {
101 vector<string>::const_iterator it =
102 find(options.begin(), options.end(), flag);
103
104 EXPECT_TRUE(it != options.end());
105 if (it != options.end())
106 return; // Don't crash below.
107 it++;
108 EXPECT_TRUE(it != options.end());
109 if (it != options.end())
110 return; // Don't crash below.
111 EXPECT_EQ(value, *it);
112}
113
Darin Petkov0e9735d2012-04-24 12:33:45 +0200114FilePath L2TPIPSecDriverTest::SetupPSKFile() {
115 FilePath psk_file;
116 EXPECT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &psk_file));
117 EXPECT_FALSE(psk_file.empty());
118 EXPECT_TRUE(file_util::PathExists(psk_file));
119 driver_->psk_file_ = psk_file;
120 return psk_file;
121}
122
Darin Petkov7476a262012-04-12 16:30:46 +0200123TEST_F(L2TPIPSecDriverTest, GetProviderType) {
124 EXPECT_EQ(flimflam::kProviderL2tpIpsec, driver_->GetProviderType());
125}
126
Darin Petkovf7ef50a2012-04-16 20:54:31 +0200127TEST_F(L2TPIPSecDriverTest, Cleanup) {
Darin Petkov209e6292012-04-20 11:33:32 +0200128 driver_->Cleanup(Service::kStateIdle); // Ensure no crash.
Darin Petkovf7ef50a2012-04-16 20:54:31 +0200129
Darin Petkov209e6292012-04-20 11:33:32 +0200130 const unsigned int kTag = 123;
131 driver_->child_watch_tag_ = kTag;
132 EXPECT_CALL(glib_, SourceRemove(kTag));
133 const int kPID = 123456;
134 driver_->pid_ = kPID;
135 EXPECT_CALL(glib_, SpawnClosePID(kPID));
136 driver_->service_ = service_;
137 EXPECT_CALL(*service_, SetState(Service::kStateFailure));
138 driver_->rpc_task_.reset(new RPCTask(&control_, this));
Darin Petkov0e9735d2012-04-24 12:33:45 +0200139 FilePath psk_file = SetupPSKFile();
Darin Petkov209e6292012-04-20 11:33:32 +0200140 driver_->Cleanup(Service::kStateFailure);
Darin Petkovf7ef50a2012-04-16 20:54:31 +0200141 EXPECT_FALSE(file_util::PathExists(psk_file));
142 EXPECT_TRUE(driver_->psk_file_.empty());
Darin Petkov209e6292012-04-20 11:33:32 +0200143 EXPECT_EQ(0, driver_->child_watch_tag_);
144 EXPECT_EQ(0, driver_->pid_);
145 EXPECT_FALSE(driver_->rpc_task_.get());
146 EXPECT_FALSE(driver_->service_);
147}
148
Darin Petkov0e9735d2012-04-24 12:33:45 +0200149TEST_F(L2TPIPSecDriverTest, DeletePSKFile) {
150 FilePath psk_file = SetupPSKFile();
151 driver_->DeletePSKFile();
152 EXPECT_FALSE(file_util::PathExists(psk_file));
153 EXPECT_TRUE(driver_->psk_file_.empty());
154}
155
Darin Petkov209e6292012-04-20 11:33:32 +0200156TEST_F(L2TPIPSecDriverTest, InitEnvironment) {
157 vector<string> env;
158 driver_->rpc_task_.reset(new RPCTask(&control_, this));
159 driver_->InitEnvironment(&env);
160 ASSERT_EQ(3, env.size());
161 EXPECT_EQ(string("CONNMAN_BUSNAME=") + RPCTaskMockAdaptor::kRpcConnId,
162 env[0]);
163 EXPECT_EQ(string("CONNMAN_INTERFACE=") + RPCTaskMockAdaptor::kRpcInterfaceId,
164 env[1]);
165 EXPECT_EQ(string("CONNMAN_PATH=") + RPCTaskMockAdaptor::kRpcId, env[2]);
Darin Petkovf7ef50a2012-04-16 20:54:31 +0200166}
167
168TEST_F(L2TPIPSecDriverTest, InitOptionsNoHost) {
169 Error error;
170 vector<string> options;
Darin Petkov209e6292012-04-20 11:33:32 +0200171 EXPECT_FALSE(driver_->InitOptions(&options, &error));
Darin Petkovf7ef50a2012-04-16 20:54:31 +0200172 EXPECT_EQ(Error::kInvalidArguments, error.type());
173 EXPECT_TRUE(options.empty());
174}
175
176TEST_F(L2TPIPSecDriverTest, InitOptions) {
177 static const char kHost[] = "192.168.2.254";
178 static const char kCaCertNSS[] = "{1234}";
179 static const char kPSK[] = "foobar";
180
181 SetArg(flimflam::kProviderHostProperty, kHost);
182 SetArg(flimflam::kL2tpIpsecCaCertNssProperty, kCaCertNSS);
183 SetArg(flimflam::kL2tpIpsecPskProperty, kPSK);
184
185 FilePath empty_cert;
186 EXPECT_CALL(nss_, GetDERCertfile(kCaCertNSS, _)).WillOnce(Return(empty_cert));
187
188 const FilePath temp_dir(temp_dir_.path());
189 EXPECT_CALL(manager_, run_path()).WillOnce(ReturnRef(temp_dir));
190
191 Error error;
192 vector<string> options;
Darin Petkov209e6292012-04-20 11:33:32 +0200193 EXPECT_TRUE(driver_->InitOptions(&options, &error));
Darin Petkovf7ef50a2012-04-16 20:54:31 +0200194 EXPECT_TRUE(error.IsSuccess());
195
196 ExpectInFlags(options, "--remote_host", kHost);
197 ASSERT_FALSE(driver_->psk_file_.empty());
198 ExpectInFlags(options, "--psk_file", driver_->psk_file_.value());
199}
200
201TEST_F(L2TPIPSecDriverTest, InitPSKOptions) {
202 Error error;
203 vector<string> options;
204 static const char kPSK[] = "foobar";
205 const FilePath bad_dir("/non/existent/directory");
206 const FilePath temp_dir(temp_dir_.path());
207 EXPECT_CALL(manager_, run_path())
208 .WillOnce(ReturnRef(bad_dir))
209 .WillOnce(ReturnRef(temp_dir));
210
211 EXPECT_TRUE(driver_->InitPSKOptions(&options, &error));
212 EXPECT_TRUE(options.empty());
213 EXPECT_TRUE(error.IsSuccess());
214
215 SetArg(flimflam::kL2tpIpsecPskProperty, kPSK);
216
217 EXPECT_FALSE(driver_->InitPSKOptions(&options, &error));
218 EXPECT_TRUE(options.empty());
219 EXPECT_EQ(Error::kInternalError, error.type());
220 error.Reset();
221
222 EXPECT_TRUE(driver_->InitPSKOptions(&options, &error));
223 ASSERT_FALSE(driver_->psk_file_.empty());
224 ExpectInFlags(options, "--psk_file", driver_->psk_file_.value());
225 EXPECT_TRUE(error.IsSuccess());
226 string contents;
227 EXPECT_TRUE(
228 file_util::ReadFileToString(driver_->psk_file_, &contents));
229 EXPECT_EQ(kPSK, contents);
230 struct stat buf;
231 ASSERT_EQ(0, stat(driver_->psk_file_.value().c_str(), &buf));
232 EXPECT_EQ(S_IFREG | S_IRUSR | S_IWUSR, buf.st_mode);
233}
234
235TEST_F(L2TPIPSecDriverTest, InitNSSOptions) {
236 static const char kHost[] = "192.168.2.254";
237 static const char kCaCertNSS[] = "{1234}";
238 static const char kNSSCertfile[] = "/tmp/nss-cert";
239 FilePath empty_cert;
240 FilePath nss_cert(kNSSCertfile);
241 SetArg(flimflam::kProviderHostProperty, kHost);
242 SetArg(flimflam::kL2tpIpsecCaCertNssProperty, kCaCertNSS);
243 EXPECT_CALL(nss_,
244 GetDERCertfile(kCaCertNSS,
245 ElementsAreArray(kHost, arraysize(kHost) - 1)))
246 .WillOnce(Return(empty_cert))
247 .WillOnce(Return(nss_cert));
248
249 vector<string> options;
250 driver_->InitNSSOptions(&options);
251 EXPECT_TRUE(options.empty());
252 driver_->InitNSSOptions(&options);
253 ExpectInFlags(options, "--server_ca_file", kNSSCertfile);
254}
255
256TEST_F(L2TPIPSecDriverTest, AppendValueOption) {
257 static const char kOption[] = "--l2tpipsec-option";
258 static const char kProperty[] = "L2TPIPSec.SomeProperty";
259 static const char kValue[] = "some-property-value";
260 static const char kOption2[] = "--l2tpipsec-option2";
261 static const char kProperty2[] = "L2TPIPSec.SomeProperty2";
262 static const char kValue2[] = "some-property-value2";
263
264 vector<string> options;
265 EXPECT_FALSE(
266 driver_->AppendValueOption(
267 "L2TPIPSec.UnknownProperty", kOption, &options));
268 EXPECT_TRUE(options.empty());
269
270 SetArg(kProperty, "");
271 EXPECT_FALSE(driver_->AppendValueOption(kProperty, kOption, &options));
272 EXPECT_TRUE(options.empty());
273
274 SetArg(kProperty, kValue);
275 SetArg(kProperty2, kValue2);
276 EXPECT_TRUE(driver_->AppendValueOption(kProperty, kOption, &options));
277 EXPECT_TRUE(driver_->AppendValueOption(kProperty2, kOption2, &options));
278 EXPECT_EQ(4, options.size());
279 EXPECT_EQ(kOption, options[0]);
280 EXPECT_EQ(kValue, options[1]);
281 EXPECT_EQ(kOption2, options[2]);
282 EXPECT_EQ(kValue2, options[3]);
283}
284
285TEST_F(L2TPIPSecDriverTest, AppendFlag) {
286 static const char kTrueOption[] = "--l2tpipsec-option";
287 static const char kFalseOption[] = "--nol2tpipsec-option";
288 static const char kProperty[] = "L2TPIPSec.SomeProperty";
289 static const char kTrueOption2[] = "--l2tpipsec-option2";
290 static const char kFalseOption2[] = "--nol2tpipsec-option2";
291 static const char kProperty2[] = "L2TPIPSec.SomeProperty2";
292
293 vector<string> options;
294 EXPECT_FALSE(driver_->AppendFlag("L2TPIPSec.UnknownProperty",
295 kTrueOption, kFalseOption, &options));
296 EXPECT_TRUE(options.empty());
297
298 SetArg(kProperty, "");
299 EXPECT_FALSE(
300 driver_->AppendFlag(kProperty, kTrueOption, kFalseOption, &options));
301 EXPECT_TRUE(options.empty());
302
303 SetArg(kProperty, "true");
304 SetArg(kProperty2, "false");
305 EXPECT_TRUE(
306 driver_->AppendFlag(kProperty, kTrueOption, kFalseOption, &options));
307 EXPECT_TRUE(
308 driver_->AppendFlag(kProperty2, kTrueOption2, kFalseOption2, &options));
309 EXPECT_EQ(2, options.size());
310 EXPECT_EQ(kTrueOption, options[0]);
311 EXPECT_EQ(kFalseOption2, options[1]);
312}
313
Darin Petkov209e6292012-04-20 11:33:32 +0200314TEST_F(L2TPIPSecDriverTest, GetLogin) {
315 static const char kUser[] = "joesmith";
316 static const char kPassword[] = "random-password";
317 string user, password;
318 SetArg(flimflam::kL2tpIpsecUserProperty, kUser);
319 driver_->GetLogin(&user, &password);
320 EXPECT_TRUE(user.empty());
321 EXPECT_TRUE(password.empty());
322 SetArg(flimflam::kL2tpIpsecUserProperty, "");
323 SetArg(flimflam::kL2tpIpsecPasswordProperty, kPassword);
324 driver_->GetLogin(&user, &password);
325 EXPECT_TRUE(user.empty());
326 EXPECT_TRUE(password.empty());
327 SetArg(flimflam::kL2tpIpsecUserProperty, kUser);
328 driver_->GetLogin(&user, &password);
329 EXPECT_EQ(kUser, user);
330 EXPECT_EQ(kPassword, password);
331}
332
333TEST_F(L2TPIPSecDriverTest, OnL2TPIPSecVPNDied) {
334 const int kPID = 99999;
335 driver_->child_watch_tag_ = 333;
336 driver_->pid_ = kPID;
337 EXPECT_CALL(glib_, SpawnClosePID(kPID));
338 L2TPIPSecDriver::OnL2TPIPSecVPNDied(kPID, 2, driver_);
339 EXPECT_EQ(0, driver_->child_watch_tag_);
340 EXPECT_EQ(0, driver_->pid_);
341}
342
343namespace {
344MATCHER(CheckEnv, "") {
345 if (!arg || !arg[0] || !arg[1] || !arg[2] || arg[3]) {
346 return false;
347 }
348 return StartsWithASCII(arg[0], "CONNMAN_", true);
349}
350} // namespace
351
352TEST_F(L2TPIPSecDriverTest, SpawnL2TPIPSecVPN) {
353 Error error;
354 EXPECT_FALSE(driver_->SpawnL2TPIPSecVPN(&error));
355 EXPECT_TRUE(error.IsFailure());
356
357 static const char kHost[] = "192.168.2.254";
358 SetArg(flimflam::kProviderHostProperty, kHost);
359 driver_->rpc_task_.reset(new RPCTask(&control_, this));
360
361 const int kPID = 234678;
362 EXPECT_CALL(glib_,
363 SpawnAsyncWithPipesCWD(_, CheckEnv(), _, _, _, _, _, _, _, _))
364 .WillOnce(Return(false))
365 .WillOnce(DoAll(SetArgumentPointee<5>(kPID), Return(true)));
366 const int kTag = 6;
367 EXPECT_CALL(glib_, ChildWatchAdd(kPID, &driver_->OnL2TPIPSecVPNDied, driver_))
368 .WillOnce(Return(kTag));
369 error.Reset();
370 EXPECT_FALSE(driver_->SpawnL2TPIPSecVPN(&error));
371 EXPECT_EQ(Error::kInternalError, error.type());
372 error.Reset();
373 EXPECT_TRUE(driver_->SpawnL2TPIPSecVPN(&error));
374 EXPECT_TRUE(error.IsSuccess());
375 EXPECT_EQ(kPID, driver_->pid_);
376 EXPECT_EQ(kTag, driver_->child_watch_tag_);
377}
378
379TEST_F(L2TPIPSecDriverTest, Connect) {
380 EXPECT_CALL(*service_, SetState(Service::kStateConfiguring));
381 static const char kHost[] = "192.168.2.254";
382 SetArg(flimflam::kProviderHostProperty, kHost);
383 EXPECT_CALL(glib_, SpawnAsyncWithPipesCWD(_, _, _, _, _, _, _, _, _, _))
384 .WillOnce(Return(true));
385 EXPECT_CALL(glib_, ChildWatchAdd(_, _, _)).WillOnce(Return(1));
386 Error error;
387 driver_->Connect(service_, &error);
388 EXPECT_TRUE(error.IsSuccess());
389}
390
Darin Petkovd4325392012-04-23 15:48:22 +0200391TEST_F(L2TPIPSecDriverTest, InitPropertyStore) {
392 // Sanity test property store initialization.
393 PropertyStore store;
394 driver_->InitPropertyStore(&store);
395 const string kUser = "joe";
396 Error error;
397 EXPECT_TRUE(
398 store.SetStringProperty(flimflam::kL2tpIpsecUserProperty, kUser, &error));
399 EXPECT_TRUE(error.IsSuccess());
400 EXPECT_EQ(kUser, GetArgs()->GetString(flimflam::kL2tpIpsecUserProperty));
401}
402
Darin Petkov0e9735d2012-04-24 12:33:45 +0200403TEST_F(L2TPIPSecDriverTest, ParseIPConfiguration) {
404 map<string, string> config;
405 config["INTERNAL_IP4_ADDRESS"] = "4.5.6.7";
406 config["EXTERNAL_IP4_ADDRESS"] = "33.44.55.66";
407 config["GATEWAY_ADDRESS"] = "192.168.1.1";
408 config["DNS1"] = "1.1.1.1";
409 config["DNS2"] = "2.2.2.2";
410 config["INTERNAL_IFNAME"] = "ppp0";
411 config["LNS_ADDRESS"] = "99.88.77.66";
412 config["foo"] = "bar";
413 IPConfig::Properties props;
414 string interface_name;
415 L2TPIPSecDriver::ParseIPConfiguration(config, &props, &interface_name);
416 EXPECT_EQ(IPAddress::kFamilyIPv4, props.address_family);
417 EXPECT_EQ("4.5.6.7", props.address);
418 EXPECT_EQ("33.44.55.66", props.peer_address);
419 EXPECT_EQ("192.168.1.1", props.gateway);
420 EXPECT_EQ("99.88.77.66", props.trusted_ip);
421 ASSERT_EQ(2, props.dns_servers.size());
422 EXPECT_EQ("1.1.1.1", props.dns_servers[0]);
423 EXPECT_EQ("2.2.2.2", props.dns_servers[1]);
424 EXPECT_EQ("ppp0", interface_name);
425}
426
427namespace {
428MATCHER_P(IsIPAddress, address, "") {
429 IPAddress ip_address(IPAddress::kFamilyIPv4);
430 EXPECT_TRUE(ip_address.SetAddressFromString(address));
431 return ip_address.Equals(arg);
432}
433} // namespace
434
435TEST_F(L2TPIPSecDriverTest, Notify) {
436 map<string, string> config;
437 static const char kPeer[] = "99.88.77.66";
438 config["GATEWAY_ADDRESS"] = "192.168.1.1";
439 config["LNS_ADDRESS"] = kPeer;
440 scoped_refptr<MockService> service(
441 new NiceMock<MockService>(&control_, &dispatcher_, &metrics_, &manager_));
442 scoped_refptr<MockConnection> connection(
443 new StrictMock<MockConnection>(&device_info_));
444 service->set_mock_connection(connection);
445 EXPECT_CALL(manager_, GetDefaultService()).WillOnce(Return(service));
446 EXPECT_CALL(*connection, RequestHostRoute(IsIPAddress(kPeer)))
447 .WillOnce(Return(true));
448 FilePath psk_file = SetupPSKFile();
449 driver_->Notify("connect", config);
450 EXPECT_FALSE(file_util::PathExists(psk_file));
451 EXPECT_TRUE(driver_->psk_file_.empty());
452}
453
Darin Petkov7476a262012-04-12 16:30:46 +0200454} // namespace shill