blob: 05bf62a333b73b0e0767b32e9af046fc8f47f349 [file] [log] [blame]
// 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/vpn/vpn_provider.h"
#include <memory>
#include <set>
#include <utility>
#include <base/stl_util.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
#include "shill/error.h"
#include "shill/fake_store.h"
#include "shill/ipconfig.h"
#include "shill/mock_adaptors.h"
#include "shill/mock_control.h"
#include "shill/mock_device_info.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/mock_profile.h"
#include "shill/routing_policy_entry.h"
#include "shill/vpn/mock_vpn_driver.h"
#include "shill/vpn/mock_vpn_service.h"
using std::string;
using testing::_;
using testing::NiceMock;
using testing::Return;
using testing::StartsWith;
namespace shill {
class VPNProviderTest : public testing::Test {
public:
VPNProviderTest()
: manager_(&control_, nullptr, &metrics_),
device_info_(&manager_),
provider_(&manager_) {
manager_.set_mock_device_info(&device_info_);
}
~VPNProviderTest() override = default;
protected:
static const char kHost[];
static const char kName[];
string GetServiceFriendlyName(const ServiceRefPtr& service) {
return service->friendly_name();
}
void SetConnectState(const ServiceRefPtr& service,
Service::ConnectState state) {
service->state_ = state;
}
void AddService(const VPNServiceRefPtr& service) {
provider_.services_.push_back(service);
}
VPNServiceRefPtr GetServiceAt(int idx) { return provider_.services_[idx]; }
size_t GetServiceCount() const { return provider_.services_.size(); }
MockControl control_;
MockMetrics metrics_;
MockManager manager_;
MockDeviceInfo device_info_;
VPNProvider provider_;
};
const char VPNProviderTest::kHost[] = "10.8.0.1";
const char VPNProviderTest::kName[] = "vpn-name";
TEST_F(VPNProviderTest, GetServiceNoType) {
KeyValueStore args;
Error e;
args.Set<string>(kTypeProperty, kTypeVPN);
ServiceRefPtr service = provider_.GetService(args, &e);
EXPECT_EQ(Error::kNotSupported, e.type());
EXPECT_FALSE(service);
}
TEST_F(VPNProviderTest, GetServiceUnsupportedType) {
KeyValueStore args;
Error e;
args.Set<string>(kTypeProperty, kTypeVPN);
args.Set<string>(kProviderTypeProperty, "unknown-vpn-type");
args.Set<string>(kProviderHostProperty, kHost);
args.Set<string>(kNameProperty, kName);
ServiceRefPtr service = provider_.GetService(args, &e);
EXPECT_EQ(Error::kNotSupported, e.type());
EXPECT_FALSE(service);
}
TEST_F(VPNProviderTest, GetService) {
KeyValueStore args;
args.Set<string>(kTypeProperty, kTypeVPN);
args.Set<string>(kProviderTypeProperty, kProviderOpenVpn);
args.Set<string>(kProviderHostProperty, kHost);
args.Set<string>(kNameProperty, kName);
{
Error error;
ServiceRefPtr service = provider_.FindSimilarService(args, &error);
EXPECT_EQ(Error::kNotFound, error.type());
EXPECT_EQ(nullptr, service);
}
EXPECT_EQ(0, GetServiceCount());
ServiceRefPtr service;
{
Error error;
EXPECT_CALL(manager_, RegisterService(_));
service = provider_.GetService(args, &error);
EXPECT_TRUE(error.IsSuccess());
ASSERT_NE(nullptr, service);
testing::Mock::VerifyAndClearExpectations(&manager_);
}
EXPECT_EQ("vpn_10_8_0_1_vpn_name", service->GetStorageIdentifier());
EXPECT_EQ(kName, GetServiceFriendlyName(service));
EXPECT_EQ(1, GetServiceCount());
// Configure the service to set its properties (including Provider.Host).
{
Error error;
service->Configure(args, &error);
EXPECT_TRUE(error.IsSuccess());
}
// None of the calls below should cause a new service to be registered.
EXPECT_CALL(manager_, RegisterService(_)).Times(0);
// A second call should return the same service.
{
Error error;
ServiceRefPtr get_service = provider_.GetService(args, &error);
EXPECT_TRUE(error.IsSuccess());
ASSERT_EQ(service, get_service);
}
EXPECT_EQ(1, GetServiceCount());
// FindSimilarService should also return this service.
{
Error error;
ServiceRefPtr similar_service = provider_.FindSimilarService(args, &error);
EXPECT_TRUE(error.IsSuccess());
EXPECT_EQ(service, similar_service);
}
EXPECT_EQ(1, GetServiceCount());
// However, CreateTemporaryService should create a different service.
{
Error error;
ServiceRefPtr temporary_service =
provider_.CreateTemporaryService(args, &error);
EXPECT_TRUE(error.IsSuccess());
EXPECT_NE(service, temporary_service);
// However this service will not be part of the provider.
EXPECT_EQ(1, GetServiceCount());
}
}
TEST_F(VPNProviderTest, RemoveService) {
scoped_refptr<MockVPNService> service0(
new MockVPNService(&manager_, nullptr));
scoped_refptr<MockVPNService> service1(
new MockVPNService(&manager_, nullptr));
scoped_refptr<MockVPNService> service2(
new MockVPNService(&manager_, nullptr));
provider_.services_.push_back(service0.get());
provider_.services_.push_back(service1.get());
provider_.services_.push_back(service2.get());
ASSERT_EQ(3, provider_.services_.size());
provider_.RemoveService(service1);
EXPECT_EQ(2, provider_.services_.size());
EXPECT_EQ(service0, provider_.services_[0]);
EXPECT_EQ(service2, provider_.services_[1]);
provider_.RemoveService(service2);
EXPECT_EQ(1, provider_.services_.size());
EXPECT_EQ(service0, provider_.services_[0]);
provider_.RemoveService(service0);
EXPECT_EQ(0, provider_.services_.size());
}
MATCHER_P(ServiceWithStorageId, storage_id, "") {
return arg->GetStorageIdentifier() == storage_id;
}
TEST_F(VPNProviderTest, CreateServicesFromProfile) {
FakeStore storage;
storage.SetString("no_type", "Name", "No Type Entry");
storage.SetString("no_vpn", "Type", "cellular");
storage.SetString("vpn_no_provider_type", "Type", "vpn");
storage.SetString("vpn_no_name", "Type", "vpn");
storage.SetString("vpn_no_name", "Provider.Type", "openvpn");
storage.SetString("vpn_no_host", "Type", "vpn");
storage.SetString("vpn_no_host", "Provider.Type", "openvpn");
storage.SetString("vpn_ho_host", "Name", "name");
storage.SetString("vpn_complete", "Type", "vpn");
storage.SetString("vpn_complete", "Provider.Type", "openvpn");
storage.SetString("vpn_complete", "Name", "name");
storage.SetString("vpn_complete", "Provider.Host", "1.2.3.4");
scoped_refptr<MockProfile> profile(new NiceMock<MockProfile>(&manager_, ""));
EXPECT_CALL(*profile, GetConstStorage()).WillRepeatedly(Return(&storage));
EXPECT_CALL(manager_, RegisterService(ServiceWithStorageId("vpn_complete")));
EXPECT_CALL(*profile, ConfigureService(ServiceWithStorageId("vpn_complete")))
.WillOnce(Return(true));
provider_.CreateServicesFromProfile(profile);
GetServiceAt(0)->driver()->args()->Set<string>(kProviderHostProperty,
"1.2.3.4");
// Calling this again should not create any more services (checked by the
// Times(1) above).
provider_.CreateServicesFromProfile(profile);
}
TEST_F(VPNProviderTest, CreateService) {
static const char kName[] = "test-vpn-service";
static const char kStorageID[] = "test_vpn_storage_id";
static const char kHost[] = "test-vpn-host";
static const char* const kTypes[] = {kProviderOpenVpn, kProviderL2tpIpsec,
kProviderThirdPartyVpn};
const size_t kTypesCount = base::size(kTypes);
EXPECT_CALL(manager_, RegisterService(_)).Times(kTypesCount);
for (auto type : kTypes) {
Error error;
VPNServiceRefPtr service =
provider_.CreateService(type, kName, kStorageID, &error);
ASSERT_NE(nullptr, service) << type;
ASSERT_TRUE(service->driver()) << type;
EXPECT_EQ(type, service->driver()->GetProviderType());
EXPECT_EQ(kName, GetServiceFriendlyName(service)) << type;
EXPECT_EQ(kStorageID, service->GetStorageIdentifier()) << type;
EXPECT_FALSE(service->IsAlwaysOnVpn(kHost)) << type;
EXPECT_TRUE(error.IsSuccess()) << type;
}
Error error;
VPNServiceRefPtr unknown_service =
provider_.CreateService("unknown-vpn-type", kName, kStorageID, &error);
EXPECT_FALSE(unknown_service);
EXPECT_EQ(Error::kNotSupported, error.type());
}
TEST_F(VPNProviderTest, CreateArcService) {
static const char kName[] = "test-vpn-service";
static const char kStorageID[] = "test_vpn_storage_id";
static const char kHost[] = "com.example.test.vpn";
EXPECT_CALL(manager_, RegisterService(_));
Error error;
VPNServiceRefPtr service =
provider_.CreateService(kProviderArcVpn, kName, kStorageID, &error);
ASSERT_NE(nullptr, service);
ASSERT_TRUE(service->driver());
service->driver()->args()->Set<string>(kProviderHostProperty, kHost);
EXPECT_EQ(kProviderArcVpn, service->driver()->GetProviderType());
EXPECT_EQ(kName, GetServiceFriendlyName(service));
EXPECT_EQ(kStorageID, service->GetStorageIdentifier());
EXPECT_TRUE(service->IsAlwaysOnVpn(kHost));
EXPECT_TRUE(error.IsSuccess());
}
TEST_F(VPNProviderTest, CreateTemporaryServiceFromProfile) {
FakeStore storage;
storage.SetString("no_vpn", "Type", "cellular");
storage.SetString("vpn_no_provider_type", "Type", "vpn");
storage.SetString("vpn_no_name", "Type", "vpn");
storage.SetString("vpn_no_name", "Provider.Type", "openvpn");
storage.SetString("vpn_no_host", "Type", "vpn");
storage.SetString("vpn_no_host", "Provider.Type", "openvpn");
storage.SetString("vpn_no_host", "Name", "name");
storage.SetString("vpn_complete", "Type", "vpn");
storage.SetString("vpn_complete", "Provider.Type", "openvpn");
storage.SetString("vpn_complete", "Name", "name");
storage.SetString("vpn_complete", "Provider.Host", "1.2.3.4");
scoped_refptr<MockProfile> profile(new NiceMock<MockProfile>(&manager_, ""));
EXPECT_CALL(*profile, GetConstStorage()).WillRepeatedly(Return(&storage));
Error error;
// Non VPN entry.
EXPECT_EQ(nullptr, provider_.CreateTemporaryServiceFromProfile(
profile, "no_vpn", &error));
EXPECT_FALSE(error.IsSuccess());
EXPECT_THAT(error.message(),
StartsWith("Unspecified or invalid network type"));
// VPN type not specified.
error.Reset();
EXPECT_EQ(nullptr, provider_.CreateTemporaryServiceFromProfile(
profile, "vpn_no_provider_type", &error));
EXPECT_FALSE(error.IsSuccess());
EXPECT_THAT(error.message(), StartsWith("VPN type not specified"));
// Name not specified.
error.Reset();
EXPECT_EQ(nullptr, provider_.CreateTemporaryServiceFromProfile(
profile, "vpn_no_name", &error));
EXPECT_FALSE(error.IsSuccess());
EXPECT_THAT(error.message(), StartsWith("Network name not specified"));
// Host not specified.
error.Reset();
EXPECT_EQ(nullptr, provider_.CreateTemporaryServiceFromProfile(
profile, "vpn_no_host", &error));
EXPECT_FALSE(error.IsSuccess());
EXPECT_THAT(error.message(), StartsWith("Host not specified"));
// Valid VPN service entry.
error.Reset();
EXPECT_NE(nullptr, provider_.CreateTemporaryServiceFromProfile(
profile, "vpn_complete", &error));
EXPECT_TRUE(error.IsSuccess());
}
TEST_F(VPNProviderTest, HasActiveService) {
EXPECT_FALSE(provider_.HasActiveService());
scoped_refptr<MockVPNService> service0(
new MockVPNService(&manager_, nullptr));
scoped_refptr<MockVPNService> service1(
new MockVPNService(&manager_, nullptr));
scoped_refptr<MockVPNService> service2(
new MockVPNService(&manager_, nullptr));
AddService(service0);
AddService(service1);
AddService(service2);
EXPECT_FALSE(provider_.HasActiveService());
SetConnectState(service1, Service::kStateAssociating);
EXPECT_TRUE(provider_.HasActiveService());
SetConnectState(service1, Service::kStateOnline);
EXPECT_TRUE(provider_.HasActiveService());
}
} // namespace shill