| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/tethering_manager.h" |
| |
| #include <math.h> |
| #include <stdint.h> |
| |
| #include <iostream> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/functional/bind.h> |
| #include <base/rand_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/time/time.h> |
| #include <chromeos/dbus/shill/dbus-constants.h> |
| #include <net-base/ip_address.h> |
| #include <net-base/ipv4_address.h> |
| #include <net-base/ipv6_address.h> |
| #include <net-base/mac_address.h> |
| |
| #include "shill/cellular/cellular_service_provider.h" |
| #include "shill/device.h" |
| #include "shill/error.h" |
| #include "shill/mac_address.h" |
| #include "shill/manager.h" |
| #include "shill/network/network_monitor.h" |
| #include "shill/network/portal_detector.h" |
| #include "shill/profile.h" |
| #include "shill/store/property_accessor.h" |
| #include "shill/technology.h" |
| #include "shill/wifi/hotspot_device.h" |
| #include "shill/wifi/hotspot_service.h" |
| #include "shill/wifi/wifi.h" |
| #include "shill/wifi/wifi_phy.h" |
| #include "shill/wifi/wifi_provider.h" |
| #include "shill/wifi/wifi_rf.h" |
| |
| namespace shill { |
| |
| namespace { |
| |
| static constexpr char kSSIDPrefix[] = "chromeOS-"; |
| // Random suffix should provide enough uniqueness to have low SSID collision |
| // possibility, while have enough anonymity among chromeOS population to make |
| // the device untrackable. Use 4 digit numbers as random SSID suffix. |
| static constexpr size_t kSSIDSuffixLength = 4; |
| // Max SSID length is 32 octets, hex encode change 1 character into 2 hex |
| // digits, thus max hex SSID length is multiplied by 2. |
| static constexpr size_t kMaxWiFiHexSSIDLength = 32 * 2; |
| static constexpr size_t kMinWiFiPassphraseLength = 8; |
| static constexpr size_t kMaxWiFiPassphraseLength = 63; |
| // Stop tethering and return error if tethering cannot be fully started within |
| // |kStartTimeout| time. This is the default value that will be used, unless it |
| // is explicitly updated by the upstream technology handler (e.g. if a complex |
| // setup that may require a longer timeout is used). |
| static constexpr base::TimeDelta kStartTimeout = base::Seconds(10); |
| // Return error if tethering cannot be fully stopped within |kStopTimeout| time. |
| static constexpr base::TimeDelta kStopTimeout = base::Seconds(5); |
| |
| // Auto disable tethering if no clients for |kAutoDisableDelay|. |
| static constexpr base::TimeDelta kAutoDisableDelay = base::Minutes(5); |
| |
| // Maximum time to wait for the upstream Network to successfully complete |
| // network validation before disabling the tethering session. |
| static constexpr base::TimeDelta kUpstreamNetworkValidationTimeout = |
| base::Minutes(1); |
| |
| // Default priority for tethering. Used for legacy API and for cases where |
| // tethering is restarted and we can't determine the previous priority. |
| static constexpr WiFiPhy::Priority kDefaultPriority = |
| WiFiPhy::Priority(WiFiPhy::Priority::kMaximumPriority); |
| |
| // Prefix used by tethering logging messages when the tethering session is |
| // stopped due to unexpected errors. This prefix is used by the anomaly detector |
| // to identify these events. |
| constexpr std::string_view kTetheringStopAnomalyDetectorPrefix = |
| "Tethering stopped unexpectly due to reason: "; |
| |
| bool StoreToConfigBool(const StoreInterface* storage, |
| const std::string& storage_id, |
| KeyValueStore* config, |
| const std::string& name) { |
| bool bool_val; |
| if (!storage->GetBool(storage_id, name, &bool_val)) |
| return false; |
| |
| config->Set<bool>(name, bool_val); |
| return true; |
| } |
| bool StoreToConfigString(const StoreInterface* storage, |
| const std::string& storage_id, |
| KeyValueStore* config, |
| const std::string& name) { |
| std::string string_val; |
| if (!storage->GetString(storage_id, name, &string_val)) |
| return false; |
| |
| config->Set<std::string>(name, string_val); |
| return true; |
| } |
| |
| // Gets the DHCP options for starting the IPv4 DHCP server at the downstream. |
| // Returns std::nullopt if the upstream is an IPv6-only network. |
| std::optional<patchpanel::Client::DHCPOptions> GetDHCPOptions( |
| const Network& network, const Service& service) { |
| const auto network_config = network.GetNetworkConfig(); |
| // Checks if upstream has IPv4 configuration and it's ready. If not, then we |
| // don't start the DHCP server. |
| if (!network_config.ipv4_address) { |
| return std::nullopt; |
| } |
| |
| patchpanel::Client::DHCPOptions options; |
| // Fill the DNS servers. |
| for (const auto& dns_server : network_config.dns_servers) { |
| const auto dns_server_ipv4 = dns_server.ToIPv4Address(); |
| if (dns_server_ipv4) { |
| options.dns_server_addresses.push_back(*dns_server_ipv4); |
| } |
| } |
| |
| // Fill the list of domain search. |
| options.domain_search_list = network_config.dns_search_domains; |
| |
| // Set the flag for "ANDROID_METERED" option. |
| options.is_android_metered = service.IsMetered(); |
| return options; |
| } |
| |
| // b/294287313: When the uplink Network is a Cellular secondary multiplexed PDN, |
| // TetheringManager must pass to patchpanel the IPv6 configuration of the uplink |
| // Network explicitly. |
| std::optional<patchpanel::Client::UplinkIPv6Configuration> |
| GetUplinkIPv6Configuration(const Network& network) { |
| // Only consider uplink Cellular Networks. |
| if (network.technology() != Technology::kCellular) { |
| return std::nullopt; |
| } |
| std::optional<net_base::IPv6CIDR> uplink_ipv6_cidr; |
| for (const auto& addr : network.GetAddresses()) { |
| if (addr.GetFamily() == net_base::IPFamily::kIPv6) { |
| uplink_ipv6_cidr = addr.ToIPv6CIDR(); |
| break; |
| } |
| } |
| // Check if the Network has an IPv6 configuration. |
| if (!uplink_ipv6_cidr) { |
| return std::nullopt; |
| } |
| patchpanel::Client::UplinkIPv6Configuration uplink_ipv6_config; |
| uplink_ipv6_config.uplink_address = *uplink_ipv6_cidr; |
| for (const auto& dns_server : network.GetDNSServers()) { |
| if (dns_server.GetFamily() == net_base::IPFamily::kIPv6) { |
| uplink_ipv6_config.dns_server_addresses.push_back( |
| *dns_server.ToIPv6Address()); |
| } |
| } |
| return uplink_ipv6_config; |
| } |
| |
| } // namespace |
| |
| TetheringManager::TetheringManager(Manager* manager) |
| : manager_(manager), |
| allowed_(false), |
| experimental_tethering_functionality_(false), |
| state_(TetheringState::kTetheringIdle), |
| upstream_network_(nullptr), |
| upstream_service_(nullptr), |
| downstream_network_started_(false), |
| hotspot_dev_(nullptr), |
| hotspot_service_up_(false), |
| stop_reason_(StopReason::kInitial) { |
| ResetConfiguration(); |
| } |
| |
| TetheringManager::~TetheringManager() = default; |
| |
| void TetheringManager::ResetConfiguration() { |
| auto_disable_ = true; |
| upstream_technology_ = Technology::kCellular; |
| std::string hex_ssid; |
| std::string passphrase; |
| |
| do { |
| uint64_t rand = base::RandInt(pow(10, kSSIDSuffixLength), INT_MAX); |
| std::string suffix = std::to_string(rand); |
| std::string ssid = |
| kSSIDPrefix + suffix.substr(suffix.size() - kSSIDSuffixLength); |
| hex_ssid = base::HexEncode(ssid.data(), ssid.size()); |
| } while (hex_ssid == hex_ssid_); |
| hex_ssid_ = hex_ssid; |
| |
| do { |
| passphrase = base::RandBytesAsString(kMinWiFiPassphraseLength >> 1); |
| passphrase = base::HexEncode(passphrase.data(), passphrase.size()); |
| } while (passphrase == passphrase_); |
| passphrase_ = passphrase; |
| |
| security_ = WiFiSecurity(WiFiSecurity::kWpa2); |
| mar_ = true; |
| stable_mac_addr_ = MACAddress::CreateRandom(); |
| band_ = WiFiBand::kAllBands; |
| downstream_device_for_test_ = std::nullopt; |
| downstream_phy_index_for_test_ = std::nullopt; |
| } |
| |
| void TetheringManager::InitPropertyStore(PropertyStore* store) { |
| HelpRegisterDerivedBool(store, kTetheringAllowedProperty, |
| &TetheringManager::GetAllowed, |
| &TetheringManager::SetAllowed); |
| HelpRegisterDerivedBool( |
| store, kExperimentalTetheringFunctionality, |
| &TetheringManager::GetExperimentalTetheringFunctionality, |
| &TetheringManager::SetExperimentalTetheringFunctionality); |
| store->RegisterDerivedKeyValueStore( |
| kTetheringConfigProperty, |
| KeyValueStoreAccessor(new CustomAccessor<TetheringManager, KeyValueStore>( |
| this, &TetheringManager::GetConfig, |
| &TetheringManager::SetAndPersistConfig))); |
| store->RegisterDerivedKeyValueStore( |
| kTetheringCapabilitiesProperty, |
| KeyValueStoreAccessor(new CustomAccessor<TetheringManager, KeyValueStore>( |
| this, &TetheringManager::GetCapabilities, nullptr))); |
| store->RegisterDerivedKeyValueStore( |
| kTetheringStatusProperty, |
| KeyValueStoreAccessor(new CustomAccessor<TetheringManager, KeyValueStore>( |
| this, &TetheringManager::GetStatus, nullptr))); |
| } |
| |
| bool TetheringManager::ToProperties(KeyValueStore* properties) const { |
| DCHECK(properties); |
| |
| if (hex_ssid_.empty() || passphrase_.empty()) { |
| LOG(ERROR) << "Missing SSID or passphrase"; |
| properties->Clear(); |
| return false; |
| } |
| |
| properties->Set<bool>(kTetheringConfAutoDisableProperty, auto_disable_); |
| properties->Set<bool>(kTetheringConfMARProperty, mar_); |
| properties->Set<std::string>(kTetheringConfSSIDProperty, hex_ssid_); |
| properties->Set<std::string>(kTetheringConfPassphraseProperty, passphrase_); |
| properties->Set<std::string>(kTetheringConfSecurityProperty, |
| security_.ToString()); |
| properties->Set<std::string>(kTetheringConfBandProperty, WiFiBandName(band_)); |
| properties->Set<std::string>(kTetheringConfUpstreamTechProperty, |
| TechnologyName(upstream_technology_)); |
| if (downstream_device_for_test_.has_value()) { |
| properties->Set<std::string>(kTetheringConfDownstreamDeviceForTestProperty, |
| *downstream_device_for_test_); |
| } |
| if (downstream_phy_index_for_test_.has_value()) { |
| properties->Set<uint32_t>(kTetheringConfDownstreamPhyIndexForTestProperty, |
| *downstream_phy_index_for_test_); |
| } |
| |
| return true; |
| } |
| |
| std::optional<bool> TetheringManager::FromProperties( |
| const KeyValueStore& properties) { |
| // sanity check. |
| auto ssid = |
| properties.GetOptionalValue<std::string>(kTetheringConfSSIDProperty); |
| if (ssid.has_value() && |
| (ssid->empty() || ssid->length() > kMaxWiFiHexSSIDLength || |
| !std::all_of(ssid->begin(), ssid->end(), ::isxdigit))) { |
| LOG(ERROR) << "Invalid SSID provided in tethering config: " << *ssid; |
| return std::nullopt; |
| } |
| |
| auto passphrase = properties.GetOptionalValue<std::string>( |
| kTetheringConfPassphraseProperty); |
| if (passphrase.has_value() && |
| (passphrase->length() < kMinWiFiPassphraseLength || |
| passphrase->length() > kMaxWiFiPassphraseLength)) { |
| LOG(ERROR) << "Passphrase provided in tethering config has invalid length: " |
| << *passphrase; |
| return std::nullopt; |
| } |
| |
| std::optional<WiFiSecurity> sec = std::nullopt; |
| if (properties.Contains<std::string>(kTetheringConfSecurityProperty)) { |
| sec = WiFiSecurity( |
| properties.Get<std::string>(kTetheringConfSecurityProperty)); |
| if (!sec->IsValid() || !(*sec == WiFiSecurity(WiFiSecurity::kWpa2) || |
| *sec == WiFiSecurity(WiFiSecurity::kWpa3) || |
| *sec == WiFiSecurity(WiFiSecurity::kWpa2Wpa3))) { |
| LOG(ERROR) << "Invalid security mode provided in tethering config: " |
| << *sec; |
| return std::nullopt; |
| } |
| } |
| |
| std::optional<WiFiBand> band = std::nullopt; |
| if (properties.Contains<std::string>(kTetheringConfBandProperty)) { |
| band = WiFiBandFromName( |
| properties.Get<std::string>(kTetheringConfBandProperty)); |
| if (*band == WiFiBand::kUnknownBand) { |
| LOG(ERROR) << "Invalid WiFi band: " << *band; |
| return std::nullopt; |
| } |
| } |
| |
| std::optional<Technology> tech = std::nullopt; |
| if (properties.Contains<std::string>(kTetheringConfUpstreamTechProperty)) { |
| tech = TechnologyFromName( |
| properties.Get<std::string>(kTetheringConfUpstreamTechProperty)); |
| // TODO(b/235762746) Add support for WiFi as an upstream technology. |
| if (*tech != Technology::kEthernet && *tech != Technology::kCellular) { |
| LOG(ERROR) << "Invalid upstream technology provided in tethering config: " |
| << *tech; |
| return std::nullopt; |
| } |
| } |
| |
| bool restart = false; |
| // update tethering config in this. |
| if (properties.Contains<bool>(kTetheringConfAutoDisableProperty) && |
| auto_disable_ != |
| properties.Get<bool>(kTetheringConfAutoDisableProperty)) { |
| // If auto disable config changed, reset inactive timer on the fly which |
| // does not require session restart. |
| auto_disable_ = properties.Get<bool>(kTetheringConfAutoDisableProperty); |
| if (state_ == TetheringState::kTetheringActive) { |
| (auto_disable_ && GetClientCount() == 0) ? StartInactiveTimer() |
| : StopInactiveTimer(); |
| } |
| } |
| |
| if (properties.Contains<bool>(kTetheringConfMARProperty) && |
| mar_ != properties.Get<bool>(kTetheringConfMARProperty)) { |
| mar_ = properties.Get<bool>(kTetheringConfMARProperty); |
| restart = true; |
| } |
| |
| if (ssid.has_value() && hex_ssid_ != *ssid) { |
| hex_ssid_ = *ssid; |
| restart = true; |
| } |
| |
| if (passphrase.has_value() && passphrase_ != *passphrase) { |
| passphrase_ = *passphrase; |
| restart = true; |
| } |
| |
| if (sec.has_value() && security_ != *sec) { |
| security_ = *sec; |
| restart = true; |
| } |
| |
| if (band.has_value() && band_ != *band) { |
| band_ = *band; |
| restart = true; |
| } |
| |
| if (tech.has_value() && upstream_technology_ != *tech) { |
| upstream_technology_ = *tech; |
| restart = true; |
| } |
| |
| if (properties.Contains<std::string>( |
| kTetheringConfDownstreamDeviceForTestProperty) && |
| downstream_device_for_test_ != |
| properties.Get<std::string>( |
| kTetheringConfDownstreamDeviceForTestProperty)) { |
| downstream_device_for_test_ = properties.Get<std::string>( |
| kTetheringConfDownstreamDeviceForTestProperty); |
| restart = true; |
| } |
| |
| if (properties.Contains<uint32_t>( |
| kTetheringConfDownstreamPhyIndexForTestProperty) && |
| downstream_phy_index_for_test_ != |
| properties.Get<uint32_t>( |
| kTetheringConfDownstreamPhyIndexForTestProperty)) { |
| downstream_phy_index_for_test_ = properties.Get<uint32_t>( |
| kTetheringConfDownstreamPhyIndexForTestProperty); |
| restart = true; |
| } |
| |
| return restart; |
| } |
| |
| KeyValueStore TetheringManager::GetConfig(Error* error) { |
| KeyValueStore config; |
| if (!ToProperties(&config)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Failed to get TetheringConfig"); |
| } |
| return config; |
| } |
| |
| bool TetheringManager::SetAndPersistConfig(const KeyValueStore& config, |
| Error* error) { |
| if (!allowed_) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kPermissionDenied, |
| "Tethering is not allowed"); |
| return false; |
| } |
| |
| const auto profile = manager_->ActiveProfile(); |
| // TODO(b/172224298): prefer using Profile::IsDefault. |
| if (profile->GetUser().empty()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kIllegalOperation, |
| "Tethering is not allowed without user profile"); |
| return false; |
| } |
| |
| auto old_ssid = hex_ssid_; |
| auto old_upstream_technology = upstream_technology_; |
| auto restart_needed = FromProperties(config); |
| if (!restart_needed.has_value()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments, |
| "Invalid tethering configuration"); |
| return false; |
| } |
| // If the SSID changes then re-randomize the stable MAC. |
| if (hex_ssid_ != old_ssid) { |
| stable_mac_addr_ = MACAddress::CreateRandom(); |
| } |
| |
| if (!Save(profile->GetStorage())) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Failed to save config to user profile"); |
| return false; |
| } |
| |
| if (restart_needed.value() && |
| (state_ == TetheringState::kTetheringActive || |
| state_ == TetheringState::kTetheringStarting)) { |
| bool bypass_upstream = false; |
| if (upstream_technology_ == old_upstream_technology && |
| upstream_technology_ == Technology::kCellular) { |
| // Do not stop upstream cellular network in session restart if upstream |
| // is not changed as PDN switching is costly. |
| bypass_upstream = true; |
| } |
| // StopTetheringSession with StopReason kConfigChange restarts tethering. |
| // Need to send D-Bus result first. So defer restart work to event loop. |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, base::BindOnce(&TetheringManager::StopTetheringSession, |
| base::Unretained(this), |
| StopReason::kConfigChange, bypass_upstream)); |
| } |
| |
| return true; |
| } |
| |
| KeyValueStore TetheringManager::GetCapabilities(Error* /* error */) { |
| return capabilities_; |
| } |
| |
| void TetheringManager::SetCapabilities(const KeyValueStore& value) { |
| if (capabilities_ == value) { |
| return; |
| } |
| capabilities_ = value; |
| manager_->TetheringCapabilitiesChanged(capabilities_); |
| } |
| |
| void TetheringManager::RefreshCapabilities() { |
| KeyValueStore caps; |
| std::vector<std::string> upstream_technologies; |
| std::vector<std::string> downstream_technologies; |
| |
| // Ethernet is always supported as an upstream technology. |
| upstream_technologies.push_back(TechnologyName(Technology::kEthernet)); |
| |
| if (manager_->cellular_service_provider()->HardwareSupportsTethering( |
| experimental_tethering_functionality_)) |
| upstream_technologies.push_back(TechnologyName(Technology::kCellular)); |
| |
| // TODO(b/244335143): This should be based on static SoC capability |
| // information. Need to revisit this when Shill has a SoC capability |
| // database. For now, use the presence of a WiFi phy as a proxy for |
| // checking if AP mode is supported. |
| auto wifi_phys = manager_->wifi_provider()->GetPhys(); |
| if (!wifi_phys.empty()) { |
| if (wifi_phys.front()->SupportAPMode() && |
| wifi_phys.front()->SupportAPSTAConcurrency()) { |
| downstream_technologies.push_back(TechnologyName(Technology::kWiFi)); |
| // Wi-Fi specific tethering capabilities. |
| // TODO(b/273351443) Add WPA2WPA3 and WPA3 security capability to |
| // supported chipset. |
| caps.Set<Strings>(kTetheringCapSecurityProperty, {kSecurityWpa2}); |
| } |
| } |
| |
| caps.Set<Strings>(kTetheringCapUpstreamProperty, upstream_technologies); |
| caps.Set<Strings>(kTetheringCapDownstreamProperty, downstream_technologies); |
| SetCapabilities(caps); |
| } |
| |
| KeyValueStore TetheringManager::GetStatus() { |
| KeyValueStore status; |
| status.Set<std::string>(kTetheringStatusStateProperty, |
| TetheringStateName(state_)); |
| |
| if (state_ != TetheringState::kTetheringActive) { |
| if (state_ == TetheringState::kTetheringIdle) { |
| status.Set<std::string>(kTetheringStatusIdleReasonProperty, |
| StopReasonToString(stop_reason_)); |
| } |
| return status; |
| } |
| |
| status.Set<std::string>(kTetheringStatusUpstreamTechProperty, |
| TechnologyName(upstream_technology_)); |
| status.Set<std::string>(kTetheringStatusDownstreamTechProperty, kTypeWifi); |
| |
| // Get stations information. |
| Stringmaps clients; |
| for (const net_base::MacAddress station : hotspot_dev_->GetStations()) { |
| Stringmap client; |
| client.insert({kTetheringStatusClientMACProperty, station.ToString()}); |
| // TODO(b/235763170): Get IP address and hostname from patchpanel |
| clients.push_back(client); |
| } |
| status.Set<Stringmaps>(kTetheringStatusClientsProperty, clients); |
| |
| return status; |
| } |
| |
| size_t TetheringManager::GetClientCount() { |
| return (hotspot_dev_ == nullptr) ? 0 : hotspot_dev_->GetStations().size(); |
| } |
| |
| void TetheringManager::SetState(TetheringState state) { |
| if (state_ == state) |
| return; |
| |
| LOG(INFO) << "State changed from " << state_ << " to " << state; |
| state_ = state; |
| |
| manager_->TetheringStatusChanged(); |
| } |
| |
| // static |
| const char* TetheringManager::TetheringStateName(const TetheringState& state) { |
| switch (state) { |
| case TetheringState::kTetheringIdle: |
| return kTetheringStateIdle; |
| case TetheringState::kTetheringStarting: |
| return kTetheringStateStarting; |
| case TetheringState::kTetheringActive: |
| return kTetheringStateActive; |
| case TetheringState::kTetheringStopping: |
| return kTetheringStateStopping; |
| case TetheringState::kTetheringRestarting: |
| return kTetheringStateRestarting; |
| default: |
| NOTREACHED() << "Unhandled tethering state " << static_cast<int>(state); |
| return "Invalid"; |
| } |
| } |
| |
| void TetheringManager::Start() {} |
| |
| void TetheringManager::Stop() { |
| StopTetheringSession(StopReason::kUserExit); |
| } |
| |
| void TetheringManager::PostSetEnabledResult(SetEnabledResult result) { |
| if (result_callback_) { |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(result_callback_), result)); |
| } |
| } |
| |
| void TetheringManager::CheckAndStartDownstreamTetheredNetwork() { |
| if (!hotspot_dev_ || !hotspot_dev_->IsServiceUp()) { |
| // Downstream hotspot device or service is not ready. |
| if (hotspot_service_up_) { |
| // Has already received the kLinkUp event, but device state is not |
| // correct, something went wrong. Terminate tethering session. |
| LOG(ERROR) << "Has received kLinkUp event from hotspot device but the " |
| "device state is not correct. Terminate tethering session"; |
| PostSetEnabledResult(SetEnabledResult::kDownstreamWiFiFailure); |
| StopTetheringSession(StopReason::kError); |
| } |
| return; |
| } |
| CHECK(hotspot_dev_->link_name()); |
| if (!upstream_network_ || !upstream_service_) { |
| return; |
| } |
| |
| std::string downstream_ifname = *hotspot_dev_->link_name(); |
| const auto& upstream_ifname = upstream_network_->interface_name(); |
| |
| std::optional<int> mtu = upstream_network_->GetNetworkConfig().mtu; |
| |
| if (downstream_network_started_) { |
| LOG(ERROR) << "Request to start downstream network " << downstream_ifname |
| << " tethered to " << upstream_ifname << " was already sent"; |
| PostSetEnabledResult(SetEnabledResult::kFailure); |
| StopTetheringSession(StopReason::kError); |
| return; |
| } |
| |
| auto dhcp_options = GetDHCPOptions(*upstream_network_, *upstream_service_); |
| auto uplink_ipv6_config = GetUplinkIPv6Configuration(*upstream_network_); |
| downstream_network_started_ = |
| manager_->patchpanel_client()->CreateTetheredNetwork( |
| downstream_ifname, upstream_ifname, dhcp_options, uplink_ipv6_config, |
| mtu, |
| base::BindOnce(&TetheringManager::OnDownstreamNetworkReady, |
| base::Unretained(this))); |
| if (!downstream_network_started_) { |
| LOG(ERROR) << "Failed requesting downstream network " << downstream_ifname |
| << " tethered to " << upstream_ifname; |
| PostSetEnabledResult(SetEnabledResult::kNetworkSetupFailure); |
| StopTetheringSession(StopReason::kDownstreamNetDisconnect); |
| return; |
| } |
| |
| LOG(INFO) << "Requested downstream network " << downstream_ifname |
| << " tethered to " << upstream_ifname; |
| } |
| |
| void TetheringManager::CheckAndPostTetheringStartResult() { |
| if (!downstream_network_fd_.is_valid()) { |
| return; |
| } |
| |
| if (!upstream_network_ || !upstream_network_->IsConnected()) { |
| PostSetEnabledResult(SetEnabledResult::kUpstreamNetworkNotAvailable); |
| StopTetheringSession(StopReason::kUpstreamNotAvailable); |
| return; |
| } |
| |
| SetState(TetheringState::kTetheringActive); |
| start_timer_callback_.Cancel(); |
| if (hotspot_dev_->GetStations().size() == 0) { |
| // Kick off inactive timer when tethering session becomes active and no |
| // clients are connected. |
| StartInactiveTimer(); |
| } |
| // If Internet connectivity has not yet been evaluated, start the network |
| // validation timer. |
| if (!IsUpstreamNetworkReady()) { |
| StartUpstreamNetworkValidationTimer(); |
| } |
| PostSetEnabledResult(SetEnabledResult::kSuccess); |
| } |
| |
| void TetheringManager::CheckAndPostTetheringStopResult() { |
| if (upstream_network_ != nullptr) { |
| return; |
| } |
| |
| // TODO(b/235762439): Routine to check other tethering modules. |
| |
| stop_timer_callback_.Cancel(); |
| if (state_ == TetheringState::kTetheringRestarting) { |
| StartTetheringSession(); |
| return; |
| } |
| |
| SetState(TetheringState::kTetheringIdle); |
| if (stop_reason_ == StopReason::kClientStop) { |
| PostSetEnabledResult(SetEnabledResult::kSuccess); |
| } |
| } |
| |
| void TetheringManager::OnStartingTetheringTimeout() { |
| SetEnabledResult result = SetEnabledResult::kFailure; |
| LOG(ERROR) << __func__ << ": tethering session start timed out"; |
| |
| if (!hotspot_dev_ || !hotspot_dev_->IsServiceUp()) { |
| result = SetEnabledResult::kDownstreamWiFiFailure; |
| } else if (!upstream_network_) { |
| result = SetEnabledResult::kUpstreamNetworkNotAvailable; |
| } else if (!upstream_network_->IsConnected()) { |
| result = SetEnabledResult::kUpstreamFailure; |
| } else if (!downstream_network_fd_.is_valid()) { |
| result = SetEnabledResult::kNetworkSetupFailure; |
| } |
| PostSetEnabledResult(result); |
| StopTetheringSession(StopReason::kStartTimeout); |
| } |
| |
| void TetheringManager::OnStartingTetheringUpdateTimeout( |
| base::TimeDelta timeout) { |
| LOG(INFO) << __func__ << ": " << timeout; |
| DCHECK(timeout > kStartTimeout); |
| |
| if (start_timer_callback_.IsCancelled()) { |
| LOG(INFO) << __func__ << ": already cancelled"; |
| return; |
| } |
| |
| if (state_ != TetheringState::kTetheringStarting) { |
| LOG(INFO) << __func__ << ": not starting"; |
| return; |
| } |
| |
| start_timer_callback_.Cancel(); |
| start_timer_callback_.Reset(base::BindOnce( |
| &TetheringManager::OnStartingTetheringTimeout, base::Unretained(this))); |
| manager_->dispatcher()->PostDelayedTask( |
| FROM_HERE, start_timer_callback_.callback(), timeout); |
| } |
| |
| void TetheringManager::FreeUpstreamNetwork() { |
| // OnNetworkDestroyed() may have been called during a ReleaseTetheringNetwork |
| // call (e.g. if connecting DUN as DEFAULT or a multiplexed DUN). |
| if (!upstream_network_) { |
| return; |
| } |
| upstream_network_->UnregisterEventHandler(this); |
| upstream_network_ = nullptr; |
| upstream_service_ = nullptr; |
| } |
| |
| void TetheringManager::OnStoppingTetheringTimeout() { |
| LOG(ERROR) << __func__ << ": cannot stop tethering session in " |
| << kStopTimeout; |
| |
| SetEnabledResult result = SetEnabledResult::kFailure; |
| if (upstream_network_ != nullptr) { |
| // TODO(b/235762746) Cellular: if the upstream cellular network already |
| // exists, use CellularServiceProvider::ReleaseTetheringNetwork() instead. |
| |
| // For other types of upstream technology like ethernet or WiFi, there is |
| // no particular cleanup other than resetting the internal state. |
| FreeUpstreamNetwork(); |
| result = SetEnabledResult::kUpstreamFailure; |
| } |
| |
| SetState(TetheringState::kTetheringIdle); |
| stop_timer_callback_.Cancel(); |
| |
| if (stop_reason_ == StopReason::kClientStop) { |
| PostSetEnabledResult(result); |
| } |
| } |
| |
| void TetheringManager::StartTetheringSession() { |
| // Reset stop reason. |
| stop_reason_ = StopReason::kInitial; |
| if (state_ != TetheringState::kTetheringIdle && |
| state_ != TetheringState::kTetheringRestarting) { |
| LOG(ERROR) << __func__ << ": unexpected tethering state " << state_; |
| PostSetEnabledResult(SetEnabledResult::kWrongState); |
| return; |
| } |
| |
| if (hotspot_dev_ || downstream_network_started_ || |
| downstream_network_fd_.is_valid()) { |
| LOG(ERROR) << "Tethering resources are not null when starting tethering " |
| "session."; |
| PostSetEnabledResult(SetEnabledResult::kFailure); |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": in state " << state_; |
| // Keep the state if it is restarting. |
| if (state_ != TetheringState::kTetheringRestarting) { |
| SetState(TetheringState::kTetheringStarting); |
| } |
| start_timer_callback_.Reset(base::BindOnce( |
| &TetheringManager::OnStartingTetheringTimeout, base::Unretained(this))); |
| manager_->dispatcher()->PostDelayedTask( |
| FROM_HERE, start_timer_callback_.callback(), kStartTimeout); |
| |
| // Prepare the downlink hotspot device. |
| hotspot_service_up_ = false; |
| const net_base::MacAddress mac_address = |
| mar_ ? MACAddress::CreateRandom().address().value() |
| : stable_mac_addr_.address().value(); |
| |
| if (downstream_device_for_test_ && downstream_phy_index_for_test_) { |
| hotspot_dev_ = manager_->wifi_provider()->CreateHotspotDeviceForTest( |
| mac_address, *downstream_device_for_test_, |
| *downstream_phy_index_for_test_, |
| base::BindRepeating(&TetheringManager::OnDownstreamDeviceEvent, |
| base::Unretained(this))); |
| } else { |
| hotspot_dev_ = manager_->wifi_provider()->CreateHotspotDevice( |
| mac_address, band_, security_, |
| base::BindRepeating(&TetheringManager::OnDownstreamDeviceEvent, |
| base::Unretained(this))); |
| } |
| if (!hotspot_dev_) { |
| LOG(ERROR) << __func__ << ": failed to create a WiFi AP interface"; |
| PostSetEnabledResult(SetEnabledResult::kDownstreamWiFiFailure); |
| StopTetheringSession(StopReason::kDownstreamLinkDisconnect); |
| return; |
| } |
| |
| if (upstream_network_) { |
| // No need to acquire a new upstream network. |
| CheckAndStartDownstreamTetheredNetwork(); |
| return; |
| } |
| |
| // Prepare the upstream network. |
| if (upstream_technology_ == Technology::kCellular) { |
| manager_->cellular_service_provider()->AcquireTetheringNetwork( |
| base::BindRepeating(&TetheringManager::OnStartingTetheringUpdateTimeout, |
| base::Unretained(this)), |
| base::BindOnce(&TetheringManager::OnUpstreamNetworkAcquired, |
| base::Unretained(this)), |
| base::BindRepeating(&TetheringManager::OnCellularUpstreamEvent, |
| base::Unretained(this)), |
| experimental_tethering_functionality_); |
| } else if (upstream_technology_ == Technology::kEthernet) { |
| const auto eth_service = manager_->GetFirstEthernetService(); |
| const auto upstream_network = |
| manager_->FindActiveNetworkFromService(eth_service); |
| const auto result = upstream_network |
| ? SetEnabledResult::kSuccess |
| : SetEnabledResult::kUpstreamNetworkNotAvailable; |
| OnUpstreamNetworkAcquired(result, upstream_network, eth_service.get()); |
| } else { |
| // TODO(b/235762746) Add support for WiFi as an upstream technology for "usb |
| // tethering" and for chipsets that support simultaneous hotspot and station |
| // modes. |
| LOG(ERROR) << __func__ << ": " << upstream_technology_ |
| << " not supported as an upstream technology"; |
| PostSetEnabledResult(SetEnabledResult::kInvalidProperties); |
| StopTetheringSession(StopReason::kError); |
| } |
| } |
| |
| void TetheringManager::OnDownstreamDeviceEnabled() { |
| // Prepare the downlink service. |
| auto freq = manager_->wifi_provider() |
| ->GetPhyAtIndex(hotspot_dev_->phy_index()) |
| ->SelectFrequency(band_); |
| |
| if (!freq.has_value()) { |
| LOG(ERROR) << __func__ << ": failed to select frequency"; |
| PostSetEnabledResult(SetEnabledResult::kDownstreamWiFiFailure); |
| StopTetheringSession(StopReason::kDownstreamLinkDisconnect); |
| return; |
| } |
| |
| std::unique_ptr<HotspotService> service = std::make_unique<HotspotService>( |
| hotspot_dev_, hex_ssid_, passphrase_, security_, freq.value()); |
| if (!hotspot_dev_->ConfigureService(std::move(service))) { |
| LOG(ERROR) << __func__ << ": failed to configure the hotspot service"; |
| PostSetEnabledResult(SetEnabledResult::kDownstreamWiFiFailure); |
| StopTetheringSession(StopReason::kDownstreamLinkDisconnect); |
| return; |
| } |
| } |
| |
| void TetheringManager::StopTetheringSession(StopReason reason, |
| bool bypass_upstream) { |
| if (state_ == TetheringState::kTetheringIdle || |
| state_ == TetheringState::kTetheringStopping) { |
| if (reason == StopReason::kClientStop) { |
| LOG(ERROR) << __func__ << ": no active or starting tethering session"; |
| PostSetEnabledResult(SetEnabledResult::kWrongState); |
| } |
| return; |
| } |
| |
| if (reason == StopReason::kError || |
| reason == StopReason::kDownstreamLinkDisconnect) { |
| LOG(ERROR) << kTetheringStopAnomalyDetectorPrefix |
| << StopReasonToString(reason); |
| } else { |
| LOG(INFO) << __func__ << ": " << StopReasonToString(reason); |
| } |
| stop_reason_ = reason; |
| if (reason == StopReason::kConfigChange) { |
| // Restart the tethering session due to config change. |
| SetState(TetheringState::kTetheringRestarting); |
| } else { |
| SetState(TetheringState::kTetheringStopping); |
| } |
| start_timer_callback_.Cancel(); |
| StopInactiveTimer(); |
| |
| // Tear down the downstream network if any. |
| // TODO(b/275645124) Add a callback to ensure that the downstream network tear |
| // down has finished. |
| downstream_network_fd_.reset(); |
| downstream_network_started_ = false; |
| |
| // Remove the downstream device if any. |
| if (hotspot_dev_) { |
| hotspot_dev_->DeconfigureService(); |
| manager_->wifi_provider()->DeleteLocalDevice(hotspot_dev_); |
| hotspot_dev_ = nullptr; |
| } |
| hotspot_service_up_ = false; |
| |
| if (bypass_upstream && state_ == TetheringState::kTetheringRestarting) { |
| // Downstream down, bypass upstream, restart tethering session immediately. |
| StartTetheringSession(); |
| return; |
| } |
| stop_timer_callback_.Reset(base::BindOnce( |
| &TetheringManager::OnStoppingTetheringTimeout, base::Unretained(this))); |
| manager_->dispatcher()->PostDelayedTask( |
| FROM_HERE, stop_timer_callback_.callback(), kStopTimeout); |
| |
| if ((upstream_network_ && |
| upstream_network_->technology() == Technology::kCellular) || |
| (!upstream_network_ && upstream_technology_ == Technology::kCellular)) { |
| // If the upstream_network_ is a cellular network type or if the current |
| // upstream technology is cellular and the upstream_network_ has not been |
| // acquired yet, ask CellularServiceProvider to release it and restore to |
| // the original PDN. |
| manager_->cellular_service_provider()->ReleaseTetheringNetwork( |
| upstream_network_, // may be nullptr if attempt is ongoing |
| base::BindOnce(&TetheringManager::OnUpstreamNetworkReleased, |
| base::Unretained(this))); |
| return; |
| } |
| |
| if (!upstream_network_) { |
| CheckAndPostTetheringStopResult(); |
| return; |
| } |
| |
| // For other types of upstream technology like ethernet or WiFi, there is |
| // no particular cleanup other than resetting the internal state. |
| OnUpstreamNetworkReleased(/*is_success=*/true); |
| } |
| |
| void TetheringManager::StartInactiveTimer() { |
| if (!auto_disable_ || !inactive_timer_callback_.IsCancelled() || |
| state_ != TetheringState::kTetheringActive) { |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": timer fires in " << kAutoDisableDelay; |
| |
| inactive_timer_callback_.Reset( |
| base::BindOnce(&TetheringManager::StopTetheringSession, |
| base::Unretained(this), StopReason::kInactive, false)); |
| manager_->dispatcher()->PostDelayedTask( |
| FROM_HERE, inactive_timer_callback_.callback(), kAutoDisableDelay); |
| } |
| |
| void TetheringManager::StopInactiveTimer() { |
| if (inactive_timer_callback_.IsCancelled()) { |
| return; |
| } |
| |
| inactive_timer_callback_.Cancel(); |
| } |
| |
| void TetheringManager::StartUpstreamNetworkValidationTimer() { |
| if (!upstream_network_validation_timer_callback_.IsCancelled() || |
| state_ != TetheringState::kTetheringActive) { |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": timer fires in " |
| << kUpstreamNetworkValidationTimeout; |
| |
| upstream_network_validation_timer_callback_.Reset(base::BindOnce( |
| &TetheringManager::StopTetheringSession, base::Unretained(this), |
| StopReason::kUpstreamNoInternet, false)); |
| manager_->dispatcher()->PostDelayedTask( |
| FROM_HERE, upstream_network_validation_timer_callback_.callback(), |
| kUpstreamNetworkValidationTimeout); |
| } |
| |
| void TetheringManager::StopUpstreamNetworkValidationTimer() { |
| if (upstream_network_validation_timer_callback_.IsCancelled()) { |
| return; |
| } |
| |
| upstream_network_validation_timer_callback_.Cancel(); |
| } |
| |
| void TetheringManager::OnPeerAssoc() { |
| if (state_ != TetheringState::kTetheringActive) { |
| return; |
| } |
| |
| manager_->TetheringStatusChanged(); |
| |
| if (GetClientCount() != 0) { |
| // At least one station associated with this hotspot, cancel the inactive |
| // timer. |
| StopInactiveTimer(); |
| } |
| } |
| |
| void TetheringManager::OnPeerDisassoc() { |
| if (state_ != TetheringState::kTetheringActive) { |
| return; |
| } |
| |
| manager_->TetheringStatusChanged(); |
| |
| if (GetClientCount() == 0) { |
| // No stations associated with this hotspot, start the inactive timer. |
| StartInactiveTimer(); |
| } |
| } |
| |
| void TetheringManager::OnDownstreamDeviceEvent(LocalDevice::DeviceEvent event, |
| const LocalDevice* device) { |
| if (!hotspot_dev_ || hotspot_dev_.get() != device) { |
| LOG(ERROR) << "Receive event from unmatched local device: " |
| << device->link_name().value_or("(no link_name)"); |
| return; |
| } |
| CHECK(device->link_name()); |
| LOG(INFO) << "TetheringManager received downstream device " |
| << *device->link_name() << " event: " << event; |
| |
| switch (event) { |
| case LocalDevice::DeviceEvent::kInterfaceDisabled: |
| case LocalDevice::DeviceEvent::kLinkDown: |
| if (state_ == TetheringState::kTetheringStarting) { |
| PostSetEnabledResult(SetEnabledResult::kDownstreamWiFiFailure); |
| } |
| StopTetheringSession(StopReason::kDownstreamLinkDisconnect); |
| break; |
| case LocalDevice::DeviceEvent::kInterfaceEnabled: |
| if (state_ != TetheringState::kTetheringStarting && |
| state_ != TetheringState::kTetheringRestarting) { |
| LOG(WARNING) << __func__ |
| << ": ignore downstream device event: " << event |
| << " in state: " << state_; |
| } else { |
| OnDownstreamDeviceEnabled(); |
| } |
| break; |
| case LocalDevice::DeviceEvent::kLinkUp: |
| hotspot_service_up_ = true; |
| if (state_ != TetheringState::kTetheringStarting && |
| state_ != TetheringState::kTetheringRestarting) { |
| LOG(WARNING) << __func__ |
| << ": ignore downstream device event: " << event |
| << " in state: " << state_; |
| } else { |
| CheckAndStartDownstreamTetheredNetwork(); |
| } |
| break; |
| case LocalDevice::DeviceEvent::kPeerConnected: |
| OnPeerAssoc(); |
| break; |
| case LocalDevice::DeviceEvent::kPeerDisconnected: |
| OnPeerDisassoc(); |
| break; |
| case LocalDevice::DeviceEvent::kLinkFailure: |
| case LocalDevice::DeviceEvent::kNetworkUp: |
| case LocalDevice::DeviceEvent::kNetworkDown: |
| case LocalDevice::DeviceEvent::kNetworkFailure: |
| LOG(WARNING) << "TetheringManager ignored unexpected " << event |
| << " event from downstream device " << *device->link_name(); |
| } |
| } |
| |
| void TetheringManager::OnDownstreamNetworkReady( |
| base::ScopedFD downstream_network_fd, |
| const patchpanel::Client::DownstreamNetwork& downstream_network) { |
| if (state_ != TetheringState::kTetheringStarting && |
| state_ != TetheringState::kTetheringRestarting) { |
| LOG(WARNING) << __func__ << ": unexpected tethering state " << state_; |
| PostSetEnabledResult(SetEnabledResult::kWrongState); |
| StopTetheringSession(StopReason::kError); |
| return; |
| } |
| |
| if (!upstream_network_) { |
| LOG(WARNING) << __func__ << ": no upstream network defined"; |
| PostSetEnabledResult(SetEnabledResult::kUpstreamNetworkNotAvailable); |
| StopTetheringSession(StopReason::kUpstreamDisconnect); |
| return; |
| } |
| |
| if (!hotspot_dev_) { |
| LOG(WARNING) << __func__ << ": no downstream device defined"; |
| PostSetEnabledResult(SetEnabledResult::kDownstreamWiFiFailure); |
| StopTetheringSession(StopReason::kDownstreamLinkDisconnect); |
| return; |
| } |
| |
| CHECK(hotspot_dev_->link_name()); |
| |
| std::string downstream_ifname = *hotspot_dev_->link_name(); |
| const auto& upstream_ifname = upstream_network_->interface_name(); |
| if (!downstream_network_fd.is_valid()) { |
| LOG(ERROR) << "Failed creating downstream network " << downstream_ifname |
| << " tethered to " << upstream_ifname; |
| PostSetEnabledResult(SetEnabledResult::kNetworkSetupFailure); |
| StopTetheringSession(StopReason::kDownstreamNetDisconnect); |
| return; |
| } |
| |
| LOG(INFO) << "Established downstream network network_id=" |
| << downstream_network.network_id << " on " << downstream_ifname |
| << " tethered to " << upstream_ifname; |
| downstream_network_fd_ = std::move(downstream_network_fd); |
| CheckAndPostTetheringStartResult(); |
| } |
| |
| void TetheringManager::OnUpstreamNetworkAcquired(SetEnabledResult result, |
| Network* network, |
| ServiceRefPtr service) { |
| if (state_ == TetheringState::kTetheringStopping) { |
| // Ignore this event when tethering start is aborted. |
| // TODO(b/323251708): cancel this callback when tethering start is aborted. |
| return; |
| } |
| |
| if (result != SetEnabledResult::kSuccess) { |
| LOG(ERROR) << __func__ << ": no upstream " << upstream_technology_ |
| << " Network available"; |
| PostSetEnabledResult(result); |
| StopTetheringSession(StopReason::kUpstreamNotAvailable); |
| return; |
| } |
| |
| DCHECK(network); |
| DCHECK(service); |
| if (!network->IsConnected()) { |
| LOG(ERROR) << __func__ << ": upstream Network was not connected"; |
| PostSetEnabledResult(SetEnabledResult::kFailure); |
| StopTetheringSession(StopReason::kUpstreamDisconnect); |
| return; |
| } |
| |
| // TODO(b/273975270): Restart portal detection if the upstream network does |
| // not have Internet access and if portal detection is no currently running. |
| |
| DCHECK(!upstream_network_); |
| DCHECK(!upstream_service_); |
| upstream_network_ = network; |
| upstream_network_->RegisterEventHandler(this); |
| upstream_service_ = service; |
| CheckAndStartDownstreamTetheredNetwork(); |
| } |
| |
| void TetheringManager::OnUpstreamNetworkReleased(bool is_success) { |
| if (!is_success) { |
| LOG(ERROR) << __func__ << ": failed to release upstream " |
| << upstream_technology_ << " Network."; |
| } |
| |
| FreeUpstreamNetwork(); |
| CheckAndPostTetheringStopResult(); |
| } |
| |
| void TetheringManager::SetEnabled(bool enabled, |
| SetEnabledResultCallback callback) { |
| if (!enabled) { |
| Disable(std::move(callback)); |
| return; |
| } |
| Enable(kDefaultPriority, std::move(callback)); |
| } |
| |
| void TetheringManager::Enable(uint32_t priority, |
| SetEnabledResultCallback callback) { |
| if (state_ == TetheringState::kTetheringStarting || |
| state_ == TetheringState::kTetheringStopping) { |
| // Reject a new action immediately if the previous one is ongoing. |
| std::move(callback).Run(SetEnabledResult::kBusy); |
| return; |
| } |
| |
| CHECK(!result_callback_); |
| result_callback_ = std::move(callback); |
| |
| if (!allowed_) { |
| LOG(ERROR) << __func__ << ": not allowed"; |
| PostSetEnabledResult(SetEnabledResult::kNotAllowed); |
| return; |
| } |
| |
| const auto profile = manager_->ActiveProfile(); |
| // TODO(b/172224298): prefer using Profile::IsDefault. |
| if (profile->GetUser().empty()) { |
| LOG(ERROR) << __func__ << ": not allowed without user profile"; |
| PostSetEnabledResult(SetEnabledResult::kNotAllowed); |
| return; |
| } |
| |
| if (!Save(profile->GetStorage())) { |
| LOG(ERROR) << __func__ << ": failed to save config to user profile"; |
| PostSetEnabledResult(SetEnabledResult::kFailure); |
| return; |
| } |
| |
| StartTetheringSession(); |
| } |
| |
| void TetheringManager::Disable(SetEnabledResultCallback callback) { |
| if (state_ == TetheringState::kTetheringStarting) { |
| // Abort tethering start, send result for the start method call first. |
| CHECK(result_callback_); |
| std::move(result_callback_).Run(SetEnabledResult::kAbort); |
| } |
| result_callback_ = std::move(callback); |
| StopTetheringSession(StopReason::kClientStop); |
| } |
| |
| // static |
| const std::string TetheringManager::SetEnabledResultName( |
| SetEnabledResult result) { |
| switch (result) { |
| case SetEnabledResult::kSuccess: |
| return kTetheringEnableResultSuccess; |
| case SetEnabledResult::kFailure: |
| return kTetheringEnableResultFailure; |
| case SetEnabledResult::kNotAllowed: |
| return kTetheringEnableResultNotAllowed; |
| case SetEnabledResult::kInvalidProperties: |
| return kTetheringEnableResultInvalidProperties; |
| case SetEnabledResult::kWrongState: |
| return kTetheringEnableResultWrongState; |
| case SetEnabledResult::kUpstreamNetworkNotAvailable: |
| return kTetheringEnableResultUpstreamNotAvailable; |
| case SetEnabledResult::kUpstreamFailure: |
| return kTetheringEnableResultUpstreamFailure; |
| case SetEnabledResult::kDownstreamWiFiFailure: |
| return kTetheringEnableResultDownstreamWiFiFailure; |
| case SetEnabledResult::kNetworkSetupFailure: |
| return kTetheringEnableResultNetworkSetupFailure; |
| case SetEnabledResult::kAbort: |
| return kTetheringEnableResultAbort; |
| case SetEnabledResult::kBusy: |
| return kTetheringEnableResultBusy; |
| } |
| } |
| |
| void TetheringManager::CheckReadiness( |
| Cellular::EntitlementCheckResultCallback callback) { |
| if (!allowed_) { |
| LOG(ERROR) << __func__ << ": not allowed"; |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), EntitlementStatus::kNotAllowed)); |
| return; |
| } |
| |
| // TODO(b/235762746) Add a selection mode for choosing the current default |
| // network as the upstream network. |
| |
| // Validate the upstream technology. |
| switch (upstream_technology_) { |
| // Valid upstream technologies |
| case Technology::kCellular: |
| case Technology::kEthernet: |
| break; |
| // Invalid upstream technology. |
| case Technology::kWiFi: |
| // TODO(b/235762746) Add support for WiFi as an upstream technology. |
| default: |
| LOG(ERROR) << __func__ << ": not supported for " << upstream_technology_ |
| << " technology"; |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), EntitlementStatus::kNotAllowed)); |
| return; |
| } |
| |
| // Check if there is an "online" network for the selected upstream technology. |
| // TODO(b/235762746) Avoid using shill Devices and instead inspects currently |
| // connected Services. |
| const auto devices = manager_->FilterByTechnology(upstream_technology_); |
| if (devices.empty()) { |
| LOG(ERROR) << __func__ << ": no Device for " << upstream_technology_; |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), |
| EntitlementStatus::kUpstreamNetworkNotAvailable)); |
| return; |
| } |
| |
| // TODO(b/235762746) For WiFi -> WiFi and Ethernet -> Ethernet tethering |
| // scenarios, this check needs to take into account which interface is |
| // used for the downstream network and which interface provides the |
| // upstream network. For now always consider the selected service of the |
| // first available device. |
| const auto service = devices[0]->selected_service(); |
| if (!service || !service->IsConnected()) { |
| LOG(ERROR) << __func__ << ": no connected Service for " |
| << upstream_technology_; |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), |
| EntitlementStatus::kUpstreamNetworkNotAvailable)); |
| return; |
| } |
| |
| // When the upstream technology is Cellular, delegate to the Provider. |
| if (upstream_technology_ == Technology::kCellular) { |
| manager_->cellular_service_provider()->TetheringEntitlementCheck( |
| std::move(callback), experimental_tethering_functionality_); |
| return; |
| } |
| |
| // Otherwise for WiFi or Ethernet, there is no other entitlement check. |
| manager_->dispatcher()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), EntitlementStatus::kReady)); |
| } |
| |
| void TetheringManager::OnCellularUpstreamEvent( |
| TetheringManager::CellularUpstreamEvent event) { |
| if (upstream_technology_ != Technology::kCellular) { |
| LOG(ERROR) << "Unexpected upstream event from cellular received"; |
| return; |
| } |
| switch (event) { |
| case TetheringManager::CellularUpstreamEvent::kUserNoLongerEntitled: |
| if (state_ == TetheringState::kTetheringActive || |
| state_ == TetheringState::kTetheringStarting) { |
| LOG(INFO) << "TetheringManager stopping session because user is no " |
| "longer entitled to tether"; |
| StopTetheringSession(StopReason::kUpstreamDisconnect); |
| } |
| break; |
| } |
| } |
| |
| // static |
| const char* TetheringManager::EntitlementStatusName(EntitlementStatus status) { |
| switch (status) { |
| case EntitlementStatus::kReady: |
| return kTetheringReadinessReady; |
| case EntitlementStatus::kNotAllowed: |
| return kTetheringReadinessNotAllowed; |
| case EntitlementStatus::kNotAllowedByCarrier: |
| return kTetheringReadinessNotAllowedByCarrier; |
| case EntitlementStatus::kNotAllowedOnFw: |
| return kTetheringReadinessNotAllowedOnFw; |
| case EntitlementStatus::kNotAllowedOnVariant: |
| return kTetheringReadinessNotAllowedOnVariant; |
| case EntitlementStatus::kNotAllowedUserNotEntitled: |
| return kTetheringReadinessNotAllowedUserNotEntitled; |
| case EntitlementStatus::kUpstreamNetworkNotAvailable: |
| return kTetheringReadinessUpstreamNetworkNotAvailable; |
| } |
| } |
| |
| void TetheringManager::LoadConfigFromProfile(const ProfileRefPtr& profile) { |
| const StoreInterface* storage = profile->GetConstStorage(); |
| if (!storage->ContainsGroup(kStorageId)) { |
| LOG(INFO) << "Tethering config is not available in the persistent " |
| "store, use default config"; |
| return; |
| } |
| |
| if (!Load(storage)) { |
| LOG(ERROR) << "Tethering config is corrupted in the persistent store, use " |
| "default config"; |
| // overwrite the corrupted config in profile with the default one. |
| if (!Save(profile->GetStorage())) { |
| LOG(ERROR) << "Failed to save config to user profile"; |
| } |
| } |
| |
| stop_reason_ = StopReason::kInitial; |
| } |
| |
| void TetheringManager::UnloadConfigFromProfile() { |
| StopTetheringSession(StopReason::kUserExit); |
| ResetConfiguration(); |
| } |
| |
| bool TetheringManager::Save(StoreInterface* storage) { |
| // |storage| should not store kTetheringConfDownstreamDeviceForTestProperty |
| // and kTetheringConfDownstreamPhyIndexForTestProperty because the properties |
| // are only used for testing. |
| |
| storage->SetBool(kStorageId, kTetheringConfAutoDisableProperty, |
| auto_disable_); |
| storage->SetBool(kStorageId, kTetheringConfMARProperty, mar_); |
| stable_mac_addr_.Save(storage, kStorageId); |
| storage->SetString(kStorageId, kTetheringConfSSIDProperty, hex_ssid_); |
| storage->SetString(kStorageId, kTetheringConfPassphraseProperty, passphrase_); |
| storage->SetString(kStorageId, kTetheringConfSecurityProperty, |
| security_.ToString()); |
| storage->SetString(kStorageId, kTetheringConfBandProperty, |
| WiFiBandName(band_)); |
| storage->SetString(kStorageId, kTetheringConfUpstreamTechProperty, |
| TechnologyName(upstream_technology_)); |
| return storage->Flush(); |
| } |
| |
| bool TetheringManager::Load(const StoreInterface* storage) { |
| // We should not load kTetheringConfDownstreamDeviceForTestProperty and |
| // kTetheringConfDownstreamPhyIndexForTestProperty from |storage| because the |
| // properties are only used for testing. |
| |
| KeyValueStore config; |
| bool valid; |
| valid = StoreToConfigBool(storage, kStorageId, &config, |
| kTetheringConfAutoDisableProperty); |
| valid = valid && StoreToConfigBool(storage, kStorageId, &config, |
| kTetheringConfMARProperty); |
| valid = valid && StoreToConfigString(storage, kStorageId, &config, |
| kTetheringConfSSIDProperty); |
| valid = valid && StoreToConfigString(storage, kStorageId, &config, |
| kTetheringConfPassphraseProperty); |
| valid = valid && StoreToConfigString(storage, kStorageId, &config, |
| kTetheringConfSecurityProperty); |
| valid = valid && StoreToConfigString(storage, kStorageId, &config, |
| kTetheringConfBandProperty); |
| valid = valid && StoreToConfigString(storage, kStorageId, &config, |
| kTetheringConfUpstreamTechProperty); |
| if (valid && !FromProperties(config).has_value()) { |
| valid = false; |
| } |
| return valid && stable_mac_addr_.Load(storage, kStorageId); |
| } |
| |
| // static |
| const char* TetheringManager::StopReasonToString(StopReason reason) { |
| switch (reason) { |
| case StopReason::kInitial: |
| return kTetheringIdleReasonInitialState; |
| case StopReason::kClientStop: |
| return kTetheringIdleReasonClientStop; |
| case StopReason::kUserExit: |
| return kTetheringIdleReasonUserExit; |
| case StopReason::kSuspend: |
| return kTetheringIdleReasonSuspend; |
| case StopReason::kUpstreamNotAvailable: |
| return kTetheringIdleReasonUpstreamNotAvailable; |
| case StopReason::kUpstreamDisconnect: |
| return kTetheringIdleReasonUpstreamDisconnect; |
| case StopReason::kUpstreamNoInternet: |
| return kTetheringIdleReasonUpstreamNoInternet; |
| case StopReason::kInactive: |
| return kTetheringIdleReasonInactive; |
| case StopReason::kError: |
| return kTetheringIdleReasonError; |
| case StopReason::kConfigChange: |
| return kTetheringIdleReasonConfigChange; |
| case StopReason::kDownstreamLinkDisconnect: |
| return kTetheringIdleReasonDownstreamLinkDisconnect; |
| case StopReason::kDownstreamNetDisconnect: |
| return kTetheringIdleReasonDownstreamNetworkDisconnect; |
| case StopReason::kStartTimeout: |
| return kTetheringIdleReasonStartTimeout; |
| default: |
| NOTREACHED() << "Unhandled stop reason " << static_cast<int>(reason); |
| return "Invalid"; |
| } |
| } |
| |
| void TetheringManager::HelpRegisterDerivedBool( |
| PropertyStore* store, |
| std::string_view name, |
| bool (TetheringManager::*get)(Error* error), |
| bool (TetheringManager::*set)(const bool&, Error*)) { |
| store->RegisterDerivedBool( |
| name, |
| BoolAccessor(new CustomAccessor<TetheringManager, bool>(this, get, set))); |
| } |
| |
| bool TetheringManager::SetAllowed(const bool& value, Error* error) { |
| if (allowed_ == value) |
| return false; |
| |
| LOG(INFO) << __func__ << " Allowed set to " << std::boolalpha << value; |
| allowed_ = value; |
| return true; |
| } |
| |
| bool TetheringManager::SetExperimentalTetheringFunctionality(const bool& value, |
| Error* error) { |
| if (experimental_tethering_functionality_ == value) |
| return false; |
| |
| LOG(INFO) << __func__ << " set to " << std::boolalpha << value; |
| experimental_tethering_functionality_ = value; |
| RefreshCapabilities(); |
| return true; |
| } |
| |
| void TetheringManager::OnNetworkValidationResult( |
| int interface_index, const NetworkMonitor::Result& result) { |
| DCHECK(upstream_network_); |
| if (IsUpstreamNetworkReady()) { |
| StopUpstreamNetworkValidationTimer(); |
| } else { |
| StartUpstreamNetworkValidationTimer(); |
| } |
| } |
| |
| void TetheringManager::OnNetworkStopped(int interface_index, bool is_failure) { |
| if (state_ == TetheringState::kTetheringIdle || |
| state_ == TetheringState::kTetheringRestarting) { |
| return; |
| } |
| StopTetheringSession(StopReason::kUpstreamDisconnect); |
| } |
| |
| void TetheringManager::OnNetworkDestroyed(int network_id, int interface_index) { |
| if (state_ == TetheringState::kTetheringIdle || |
| state_ == TetheringState::kTetheringRestarting) { |
| return; |
| } |
| upstream_network_ = nullptr; |
| upstream_service_ = nullptr; |
| StopTetheringSession(StopReason::kUpstreamDisconnect); |
| } |
| |
| bool TetheringManager::IsUpstreamNetworkReady() { |
| if (!upstream_network_ || !upstream_network_->IsConnected()) { |
| // Upstream network was not yet acquired or is not connected; |
| return false; |
| } |
| const auto validation_result = upstream_network_->network_validation_result(); |
| if (!validation_result) { |
| // Internet connectivity has not yet been evaluated. |
| return false; |
| } |
| switch (validation_result->validation_state) { |
| case PortalDetector::ValidationState::kInternetConnectivity: |
| return true; |
| case PortalDetector::ValidationState::kPortalRedirect: |
| if (upstream_network_->technology() == Technology::kCellular) { |
| // b/301648519: Some Cellular carriers use portal redirection flows for |
| // asking the user to enable or buy a tethering data plan. This flow is |
| // not handled natively in ChromeOS, but the network is nonetheless |
| // considered ready. |
| return true; |
| } |
| return false; |
| case PortalDetector::ValidationState::kNoConnectivity: |
| return false; |
| case PortalDetector::ValidationState::kPortalSuspected: |
| return false; |
| } |
| } |
| |
| void TetheringManager::OnNetworkValidationStop(int interface_index, |
| bool is_failure) { |
| // If network validation fails on the upstream network, do no wait for |
| // the |upstream_network_validation_timer_callback_| to fire and terminate |
| // the session immediately. |
| if (is_failure) { |
| StopTetheringSession(StopReason::kUpstreamNoInternet); |
| } |
| } |
| |
| void TetheringManager::OnNetworkValidationStart(int interface_index, |
| bool is_failure) { |
| // If network validation fails on the upstream network, do no wait for |
| // the |upstream_network_validation_timer_callback_| to fire and terminate |
| // the session immediately. |
| if (is_failure) { |
| StopTetheringSession(StopReason::kUpstreamNoInternet); |
| } |
| } |
| |
| } // namespace shill |