| // 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/third_party_vpn_driver.h" |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include <utility> |
| |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "shill/connection.h" |
| #include "shill/control_interface.h" |
| #include "shill/device_info.h" |
| #include "shill/error.h" |
| #include "shill/file_io.h" |
| #include "shill/ipconfig.h" |
| #include "shill/logging.h" |
| #include "shill/manager.h" |
| #include "shill/net/io_handler_factory.h" |
| #include "shill/property_accessor.h" |
| #include "shill/store_interface.h" |
| #include "shill/virtual_device.h" |
| #include "shill/vpn/vpn_provider.h" |
| #include "shill/vpn/vpn_service.h" |
| |
| namespace shill { |
| |
| namespace Logging { |
| |
| static auto kModuleLogScope = ScopeLogger::kVPN; |
| static std::string ObjectID(const ThirdPartyVpnDriver* v) { |
| return "(third_party_vpn_driver)"; |
| } |
| |
| } // namespace Logging |
| |
| namespace { |
| |
| const int32_t kConstantMaxMtu = (1 << 16) - 1; |
| const int32_t kConnectTimeoutSeconds = 60 * 5; |
| |
| std::string IPAddressFingerprint(const IPAddress& address) { |
| static const char* const hex_to_bin[] = { |
| "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", |
| "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"}; |
| std::string fingerprint; |
| const size_t address_length = address.address().GetLength(); |
| const uint8_t* raw_address = address.address().GetConstData(); |
| for (size_t i = 0; i < address_length; ++i) { |
| fingerprint += hex_to_bin[raw_address[i] >> 4]; |
| fingerprint += hex_to_bin[raw_address[i] & 0xf]; |
| } |
| return fingerprint.substr(0, address.prefix()); |
| } |
| |
| } // namespace |
| |
| const VPNDriver::Property ThirdPartyVpnDriver::kProperties[] = { |
| {kProviderHostProperty, 0}, |
| {kProviderTypeProperty, 0}, |
| {kExtensionNameProperty, 0}, |
| {kConfigurationNameProperty, 0}}; |
| |
| ThirdPartyVpnDriver* ThirdPartyVpnDriver::active_client_ = nullptr; |
| |
| ThirdPartyVpnDriver::ThirdPartyVpnDriver(Manager* manager, |
| ProcessManager* process_manager) |
| : VPNDriver(manager, process_manager, kProperties, base::size(kProperties)), |
| tun_fd_(-1), |
| ip_properties_set_(false), |
| io_handler_factory_(IOHandlerFactory::GetInstance()), |
| parameters_expected_(false), |
| reconnect_supported_(false), |
| link_down_(false) { |
| file_io_ = FileIO::GetInstance(); |
| } |
| |
| ThirdPartyVpnDriver::~ThirdPartyVpnDriver() { |
| Cleanup(Service::kStateIdle, Service::kFailureNone, |
| Service::kErrorDetailsNone); |
| } |
| |
| void ThirdPartyVpnDriver::InitPropertyStore(PropertyStore* store) { |
| VPNDriver::InitPropertyStore(store); |
| store->RegisterDerivedString( |
| kObjectPathSuffixProperty, |
| StringAccessor( |
| new CustomWriteOnlyAccessor<ThirdPartyVpnDriver, std::string>( |
| this, &ThirdPartyVpnDriver::SetExtensionId, |
| &ThirdPartyVpnDriver::ClearExtensionId, nullptr))); |
| } |
| |
| bool ThirdPartyVpnDriver::Load(const StoreInterface* storage, |
| const std::string& storage_id) { |
| bool return_value = VPNDriver::Load(storage, storage_id); |
| if (adaptor_interface_ == nullptr) { |
| storage->GetString(storage_id, kObjectPathSuffixProperty, |
| &object_path_suffix_); |
| adaptor_interface_ = control_interface()->CreateThirdPartyVpnAdaptor(this); |
| } |
| return return_value; |
| } |
| |
| bool ThirdPartyVpnDriver::Save(StoreInterface* storage, |
| const std::string& storage_id, |
| bool save_credentials) { |
| bool return_value = VPNDriver::Save(storage, storage_id, save_credentials); |
| storage->SetString(storage_id, kObjectPathSuffixProperty, |
| object_path_suffix_); |
| return return_value; |
| } |
| |
| void ThirdPartyVpnDriver::ClearExtensionId(Error* error) { |
| error->Populate(Error::kNotSupported, |
| "Clearing extension id is not supported."); |
| } |
| |
| bool ThirdPartyVpnDriver::SetExtensionId(const std::string& value, |
| Error* error) { |
| if (adaptor_interface_ == nullptr) { |
| object_path_suffix_ = value; |
| adaptor_interface_ = control_interface()->CreateThirdPartyVpnAdaptor(this); |
| return true; |
| } |
| error->Populate(Error::kAlreadyExists, "Extension ID is set"); |
| return false; |
| } |
| |
| void ThirdPartyVpnDriver::UpdateConnectionState( |
| Service::ConnectState connection_state, std::string* error_message) { |
| if (active_client_ != this) { |
| error_message->append("Unexpected call"); |
| return; |
| } |
| if (service() && connection_state == Service::kStateFailure) { |
| service()->SetState(connection_state); |
| Cleanup(Service::kStateFailure, Service::kFailureNone, |
| Service::kErrorDetailsNone); |
| } else if (!service() || connection_state != Service::kStateOnline) { |
| // We expect "failure" and "connected" messages from the client, but we |
| // only set state for these "failure" messages. "connected" message (which |
| // is corresponding to kStateOnline here) will simply be ignored. |
| error_message->append("Invalid argument"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::SendPacket(const std::vector<uint8_t>& ip_packet, |
| std::string* error_message) { |
| if (active_client_ != this) { |
| error_message->append("Unexpected call"); |
| return; |
| } else if (tun_fd_ < 0) { |
| error_message->append("Device not open"); |
| return; |
| } else if (file_io_->Write(tun_fd_, ip_packet.data(), ip_packet.size()) != |
| static_cast<ssize_t>(ip_packet.size())) { |
| error_message->append("Partial write"); |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kError)); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::ProcessIp( |
| const std::map<std::string, std::string>& parameters, |
| const char* key, |
| std::string* target, |
| bool mandatory, |
| std::string* error_message) { |
| // TODO(kaliamoorthi): Add IPV6 support. |
| auto it = parameters.find(key); |
| if (it != parameters.end()) { |
| if (IPAddress(parameters.at(key)).family() == IPAddress::kFamilyIPv4) { |
| *target = parameters.at(key); |
| } else { |
| error_message->append(key).append(" is not a valid IP;"); |
| } |
| } else if (mandatory) { |
| error_message->append(key).append(" is missing;"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::ProcessIPArray( |
| const std::map<std::string, std::string>& parameters, |
| const char* key, |
| char delimiter, |
| std::vector<std::string>* target, |
| bool mandatory, |
| std::string* error_message, |
| std::string* warning_message) { |
| std::vector<std::string> string_array; |
| auto it = parameters.find(key); |
| if (it != parameters.end()) { |
| string_array = |
| base::SplitString(parameters.at(key), std::string{delimiter}, |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| // Eliminate invalid IPs |
| for (auto value = string_array.begin(); value != string_array.end();) { |
| if (IPAddress(*value).family() != IPAddress::kFamilyIPv4) { |
| warning_message->append(*value + " for " + key + " is invalid;"); |
| value = string_array.erase(value); |
| } else { |
| ++value; |
| } |
| } |
| |
| if (!string_array.empty()) { |
| target->swap(string_array); |
| } else if (mandatory) { |
| error_message->append(key).append(" has no valid values or is empty;"); |
| } |
| } else if (mandatory) { |
| error_message->append(key).append(" is missing;"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::ProcessIPArrayCIDR( |
| const std::map<std::string, std::string>& parameters, |
| const char* key, |
| char delimiter, |
| std::vector<std::string>* target, |
| bool mandatory, |
| std::string* error_message, |
| std::string* warning_message) { |
| std::vector<std::string> string_array; |
| IPAddress address(IPAddress::kFamilyIPv4); |
| auto it = parameters.find(key); |
| if (it != parameters.end()) { |
| string_array = |
| base::SplitString(parameters.at(key), std::string{delimiter}, |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| // Eliminate invalid IPs |
| for (auto value = string_array.begin(); value != string_array.end();) { |
| if (!address.SetAddressAndPrefixFromString(*value)) { |
| warning_message->append(*value + " for " + key + " is invalid;"); |
| value = string_array.erase(value); |
| continue; |
| } |
| const std::string cidr_key = IPAddressFingerprint(address); |
| if (known_cidrs_.find(cidr_key) != known_cidrs_.end()) { |
| warning_message->append("Duplicate entry for " + *value + " in " + key + |
| " found;"); |
| value = string_array.erase(value); |
| } else { |
| known_cidrs_.insert(cidr_key); |
| ++value; |
| } |
| } |
| |
| if (!string_array.empty()) { |
| target->swap(string_array); |
| } else { |
| error_message->append(key).append(" has no valid values or is empty;"); |
| } |
| } else if (mandatory) { |
| error_message->append(key).append(" is missing;"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::ProcessSearchDomainArray( |
| const std::map<std::string, std::string>& parameters, |
| const char* key, |
| char delimiter, |
| std::vector<std::string>* target, |
| bool mandatory, |
| std::string* error_message) { |
| std::vector<std::string> string_array; |
| auto it = parameters.find(key); |
| if (it != parameters.end()) { |
| string_array = |
| base::SplitString(parameters.at(key), std::string{delimiter}, |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| if (!string_array.empty()) { |
| target->swap(string_array); |
| } else { |
| error_message->append(key).append(" has no valid values or is empty;"); |
| } |
| } else if (mandatory) { |
| error_message->append(key).append(" is missing;"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::ProcessInt32( |
| const std::map<std::string, std::string>& parameters, |
| const char* key, |
| int32_t* target, |
| int32_t min_value, |
| int32_t max_value, |
| bool mandatory, |
| std::string* error_message) { |
| int32_t value = 0; |
| auto it = parameters.find(key); |
| if (it != parameters.end()) { |
| if (base::StringToInt(parameters.at(key), &value) && value >= min_value && |
| value <= max_value) { |
| *target = value; |
| } else { |
| error_message->append(key).append(" not in expected range;"); |
| } |
| } else if (mandatory) { |
| error_message->append(key).append(" is missing;"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::ProcessBoolean( |
| const std::map<std::string, std::string>& parameters, |
| const char* key, |
| bool* target, |
| bool mandatory, |
| std::string* error_message) { |
| auto it = parameters.find(key); |
| if (it != parameters.end()) { |
| std::string str_value = parameters.at(key); |
| if (str_value == "true") { |
| *target = true; |
| } else if (str_value == "false") { |
| *target = false; |
| } else { |
| error_message->append(key).append(" not a valid boolean;"); |
| } |
| } else if (mandatory) { |
| error_message->append(key).append(" is missing;"); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::SetParameters( |
| const std::map<std::string, std::string>& parameters, |
| std::string* error_message, |
| std::string* warning_message) { |
| // TODO(kaliamoorthi): Add IPV6 support. |
| if (!parameters_expected_ || active_client_ != this) { |
| error_message->append("Unexpected call"); |
| return; |
| } |
| |
| ip_properties_ = IPConfig::Properties(); |
| ip_properties_.address_family = IPAddress::kFamilyIPv4; |
| |
| ProcessIp(parameters, kAddressParameterThirdPartyVpn, &ip_properties_.address, |
| true, error_message); |
| ProcessIp(parameters, kBroadcastAddressParameterThirdPartyVpn, |
| &ip_properties_.broadcast_address, false, error_message); |
| |
| ip_properties_.gateway = ip_properties_.address; |
| |
| ProcessInt32(parameters, kSubnetPrefixParameterThirdPartyVpn, |
| &ip_properties_.subnet_prefix, 0, 32, true, error_message); |
| ProcessInt32(parameters, kMtuParameterThirdPartyVpn, &ip_properties_.mtu, |
| IPConfig::kMinIPv4MTU, kConstantMaxMtu, false, error_message); |
| |
| ProcessSearchDomainArray(parameters, kDomainSearchParameterThirdPartyVpn, |
| kNonIPDelimiter, &ip_properties_.domain_search, |
| false, error_message); |
| ProcessIPArray(parameters, kDnsServersParameterThirdPartyVpn, kIPDelimiter, |
| &ip_properties_.dns_servers, false, error_message, |
| warning_message); |
| |
| known_cidrs_.clear(); |
| |
| ProcessIPArrayCIDR(parameters, kExclusionListParameterThirdPartyVpn, |
| kIPDelimiter, &ip_properties_.exclusion_list, true, |
| error_message, warning_message); |
| if (!ip_properties_.exclusion_list.empty()) { |
| // The first excluded IP is used to find the default gateway. The logic that |
| // finds the default gateway does not work for default route "0.0.0.0/0". |
| // Hence, this code ensures that the first IP is not default. |
| IPAddress address(ip_properties_.address_family); |
| address.SetAddressAndPrefixFromString(ip_properties_.exclusion_list[0]); |
| if (address.IsDefault() && !address.prefix()) { |
| if (ip_properties_.exclusion_list.size() > 1) { |
| swap(ip_properties_.exclusion_list[0], |
| ip_properties_.exclusion_list[1]); |
| } else { |
| // When there is only a single entry which is a default address, it can |
| // be cleared since the default behavior is to not route any traffic to |
| // the tunnel interface. |
| ip_properties_.exclusion_list.clear(); |
| } |
| } |
| } |
| |
| reconnect_supported_ = false; |
| ProcessBoolean(parameters, kReconnectParameterThirdPartyVpn, |
| &reconnect_supported_, false, error_message); |
| |
| std::vector<std::string> inclusion_list; |
| ProcessIPArrayCIDR(parameters, kInclusionListParameterThirdPartyVpn, |
| kIPDelimiter, &inclusion_list, true, error_message, |
| warning_message); |
| |
| IPAddress ip_address(ip_properties_.address_family); |
| IPConfig::Route route; |
| route.gateway = ip_properties_.gateway; |
| for (const auto& value : inclusion_list) { |
| ip_address.SetAddressAndPrefixFromString(value); |
| ip_address.IntoString(&route.host); |
| route.prefix = ip_address.prefix(); |
| ip_properties_.routes.push_back(route); |
| } |
| |
| if (error_message->empty()) { |
| manager()->vpn_provider()->SetDefaultRoutingPolicy(&ip_properties_); |
| ip_properties_.default_route = false; |
| ip_properties_.blackhole_ipv6 = true; |
| device_->SelectService(service()); |
| device_->UpdateIPConfig(ip_properties_); |
| device_->SetLooseRouting(true); |
| StopConnectTimeout(); |
| |
| if (!ip_properties_set_) { |
| ip_properties_set_ = true; |
| metrics()->SendEnumToUMA(Metrics::kMetricVpnDriver, |
| Metrics::kVpnDriverThirdParty, |
| Metrics::kMetricVpnDriverMax); |
| } |
| } |
| } |
| |
| void ThirdPartyVpnDriver::OnInput(InputData* data) { |
| if (data->len <= 0) { |
| return; |
| } |
| |
| // Not all Chrome apps can properly handle being passed IPv6 packets. This |
| // usually should not be an issue because we prevent IPv6 traffic from being |
| // routed to this VPN. However, the kernel itself can sometimes send IPv6 |
| // packets to an interface--even before we set up our routing |
| // rules. Therefore, we drop non-IPv4 traffic here. |
| // |
| // See from RFC 791 Section 3.1 that the high nibble of the first byte in an |
| // IP header represents the IP version (4 in this case). |
| if ((data->buf[0] & 0xf0) != 0x40) { |
| SLOG(this, 1) << "Dropping non-IPv4 packet"; |
| return; |
| } |
| |
| // TODO(kaliamoorthi): This is not efficient, transfer the descriptor over to |
| // chrome browser or use a pipe in between. Avoid using DBUS for packet |
| // transfer. |
| std::vector<uint8_t> ip_packet(data->buf, data->buf + data->len); |
| adaptor_interface_->EmitPacketReceived(ip_packet); |
| } |
| |
| void ThirdPartyVpnDriver::OnInputError(const std::string& error) { |
| LOG(ERROR) << error; |
| CHECK_EQ(active_client_, this); |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kError)); |
| } |
| |
| void ThirdPartyVpnDriver::Cleanup(Service::ConnectState state, |
| Service::ConnectFailure failure, |
| const std::string& error_details) { |
| SLOG(this, 2) << __func__ << "(" << Service::ConnectStateToString(state) |
| << ", " << error_details << ")"; |
| StopConnectTimeout(); |
| int interface_index = -1; |
| manager()->RemoveDefaultServiceObserver(this); |
| if (device_) { |
| interface_index = device_->interface_index(); |
| device_->DropConnection(); |
| device_->SetEnabled(false); |
| device_ = nullptr; |
| } |
| if (interface_index >= 0) { |
| manager()->device_info()->DeleteInterface(interface_index); |
| } |
| tunnel_interface_.clear(); |
| if (service()) { |
| if (state == Service::kStateFailure) { |
| service()->SetErrorDetails(error_details); |
| service()->SetFailure(failure); |
| } else { |
| service()->SetState(state); |
| } |
| set_service(nullptr); |
| } |
| if (tun_fd_ > 0) { |
| file_io_->Close(tun_fd_); |
| tun_fd_ = -1; |
| } |
| io_handler_.reset(); |
| if (active_client_ == this) { |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kDisconnected)); |
| active_client_ = nullptr; |
| } |
| parameters_expected_ = false; |
| link_down_ = false; |
| reconnect_supported_ = false; |
| } |
| |
| void ThirdPartyVpnDriver::Connect(const VPNServiceRefPtr& service, |
| Error* error) { |
| SLOG(this, 2) << __func__; |
| CHECK(adaptor_interface_); |
| CHECK(!active_client_); |
| StartConnectTimeout(kConnectTimeoutSeconds); |
| ip_properties_ = IPConfig::Properties(); |
| ip_properties_set_ = false; |
| set_service(service); |
| service->SetState(Service::kStateConfiguring); |
| if (!manager()->device_info()->CreateTunnelInterface(&tunnel_interface_)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInternalError, |
| "Could not create tunnel interface."); |
| Cleanup(Service::kStateFailure, Service::kFailureInternal, |
| "Unable to create tun interface"); |
| } |
| // Wait for the ClaimInterface callback to continue the connection process. |
| } |
| |
| bool ThirdPartyVpnDriver::ClaimInterface(const std::string& link_name, |
| int interface_index) { |
| if (link_name != tunnel_interface_) { |
| return false; |
| } |
| CHECK(!active_client_); |
| |
| SLOG(this, 2) << "Claiming " << link_name << " for third party VPN tunnel"; |
| |
| CHECK(!device_); |
| device_ = new VirtualDevice(manager(), link_name, interface_index, |
| Technology::kVPN); |
| device_->SetEnabled(true); |
| |
| tun_fd_ = manager()->device_info()->OpenTunnelInterface(tunnel_interface_); |
| if (tun_fd_ < 0) { |
| Cleanup(Service::kStateFailure, Service::kFailureInternal, |
| "Unable to open tun interface"); |
| } else { |
| io_handler_.reset(io_handler_factory_->CreateIOInputHandler( |
| tun_fd_, |
| base::Bind(&ThirdPartyVpnDriver::OnInput, base::Unretained(this)), |
| base::Bind(&ThirdPartyVpnDriver::OnInputError, |
| base::Unretained(this)))); |
| active_client_ = this; |
| parameters_expected_ = true; |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kConnected)); |
| manager()->AddDefaultServiceObserver(this); |
| } |
| return true; |
| } |
| |
| void ThirdPartyVpnDriver::Disconnect() { |
| SLOG(this, 2) << __func__; |
| CHECK(adaptor_interface_); |
| if (active_client_ == this) { |
| Cleanup(Service::kStateIdle, Service::kFailureNone, |
| Service::kErrorDetailsNone); |
| } |
| } |
| |
| std::string ThirdPartyVpnDriver::GetProviderType() const { |
| return std::string(kProviderThirdPartyVpn); |
| } |
| |
| void ThirdPartyVpnDriver::TriggerReconnect(const ServiceRefPtr& new_service) { |
| StartConnectTimeout(kConnectTimeoutSeconds); |
| SLOG(this, 2) << __func__ << " - requesting reconnection via " |
| << new_service->log_name(); |
| if (link_down_) { |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kLinkUp)); |
| link_down_ = false; |
| } else { |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kLinkChanged)); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::OnDefaultServiceChanged( |
| const ServiceRefPtr& /*logical_service*/, |
| bool /*logical_service_changed*/, |
| const ServiceRefPtr& physical_service, |
| bool physical_service_changed) { |
| if (!service() || !device_ || !physical_service_changed) |
| return; |
| |
| if (!reconnect_supported_) { |
| Cleanup(Service::kStateFailure, Service::kFailureInternal, |
| "Underlying network disconnected."); |
| return; |
| } |
| |
| device_->SetServiceState(Service::kStateConfiguring); |
| device_->ResetConnection(); |
| |
| if (physical_service && physical_service->state() == Service::kStateOnline) { |
| // The original service is no longer the default, but manager was able |
| // to find another physical service that is already Online. |
| // Ask the vpnProvider to reconnect. |
| TriggerReconnect(physical_service); |
| } else { |
| // The default physical service went away, and nothing else is available |
| // right now. All we can do is wait. |
| if (link_down_) |
| return; |
| StopConnectTimeout(); |
| SLOG(this, 2) << __func__ << " - physical connection lost"; |
| link_down_ = true; |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kLinkDown)); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::OnDefaultServiceStateChanged( |
| const ServiceRefPtr& service) { |
| if (link_down_ && service->state() == Service::kStateOnline) |
| TriggerReconnect(service); |
| } |
| |
| void ThirdPartyVpnDriver::OnBeforeSuspend(const ResultCallback& callback) { |
| if (service() && reconnect_supported_) { |
| // FIXME: Currently the VPN app receives this message at the same time |
| // as the resume message, even if shill adds a delay to hold off the |
| // suspend sequence. |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kSuspend)); |
| } |
| callback.Run(Error(Error::kSuccess)); |
| } |
| |
| void ThirdPartyVpnDriver::OnAfterResume() { |
| if (service() && reconnect_supported_) { |
| // Transition back to Configuring state so that the app can perform |
| // DNS lookups and reconnect. |
| device_->SetServiceState(Service::kStateConfiguring); |
| device_->ResetConnection(); |
| StartConnectTimeout(kConnectTimeoutSeconds); |
| |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kResume)); |
| } |
| } |
| |
| void ThirdPartyVpnDriver::OnConnectTimeout() { |
| SLOG(this, 2) << __func__; |
| VPNDriver::OnConnectTimeout(); |
| adaptor_interface_->EmitPlatformMessage( |
| static_cast<uint32_t>(PlatformMessage::kError)); |
| Cleanup(Service::kStateFailure, Service::kFailureConnect, |
| "Connection timed out"); |
| } |
| |
| } // namespace shill |