blob: ef6d82f312dbd2ca041406c276030236a6506cfd [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import android.net.IpConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.os.Environment;
import android.util.Log;
import android.util.SparseArray;
import com.android.server.net.IpConfigStore;
import com.android.server.wifi.hotspot2.LegacyPasspointConfig;
import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class provides the API's to load network configurations from legacy store
* mechanism (Pre O release).
* This class loads network configurations from:
* 1. /data/misc/wifi/networkHistory.txt
* 2. /data/misc/wifi/wpa_supplicant.conf
* 3. /data/misc/wifi/ipconfig.txt
* 4. /data/misc/wifi/PerProviderSubscription.conf
*
* The order of invocation of the public methods during migration is the following:
* 1. Check if legacy stores are present using {@link #areStoresPresent()}.
* 2. Load all the store data using {@link #read()}
* 3. Write the store data to the new store.
* 4. Remove all the legacy stores using {@link #removeStores()}
*
* NOTE: This class should only be used from WifiConfigManager and is not thread-safe!
*
* TODO(b/31065385): Passpoint config store data migration & deletion.
*/
public class WifiConfigStoreLegacy {
/**
* Log tag.
*/
private static final String TAG = "WifiConfigStoreLegacy";
/**
* NetworkHistory config store file path.
*/
private static final File NETWORK_HISTORY_FILE =
new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
/**
* Passpoint config store file path.
*/
private static final File PPS_FILE =
new File(Environment.getDataMiscDirectory(), "wifi/PerProviderSubscription.conf");
/**
* IpConfig config store file path.
*/
private static final File IP_CONFIG_FILE =
new File(Environment.getDataMiscDirectory(), "wifi/ipconfig.txt");
/**
* List of external dependencies for WifiConfigManager.
*/
private final WifiNetworkHistory mWifiNetworkHistory;
private final WifiNative mWifiNative;
private final IpConfigStoreWrapper mIpconfigStoreWrapper;
private final LegacyPasspointConfigParser mPasspointConfigParser;
/**
* Used to help mocking the static methods of IpconfigStore.
*/
public static class IpConfigStoreWrapper {
/**
* Read IP configurations from Ip config store.
*/
public SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
return IpConfigStore.readIpAndProxyConfigurations(filePath);
}
}
WifiConfigStoreLegacy(WifiNetworkHistory wifiNetworkHistory,
WifiNative wifiNative, IpConfigStoreWrapper ipConfigStore,
LegacyPasspointConfigParser passpointConfigParser) {
mWifiNetworkHistory = wifiNetworkHistory;
mWifiNative = wifiNative;
mIpconfigStoreWrapper = ipConfigStore;
mPasspointConfigParser = passpointConfigParser;
}
/**
* Helper function to lookup the WifiConfiguration object from configKey to WifiConfiguration
* object map using the hashcode of the configKey.
*
* @param configurationMap Map of configKey to WifiConfiguration object.
* @param hashCode hash code of the configKey to match.
* @return
*/
private static WifiConfiguration lookupWifiConfigurationUsingConfigKeyHash(
Map<String, WifiConfiguration> configurationMap, int hashCode) {
for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
if (entry.getKey() != null && entry.getKey().hashCode() == hashCode) {
return entry.getValue();
}
}
return null;
}
/**
* Helper function to load {@link IpConfiguration} data from the ip config store file and
* populate the provided configuration map.
*
* @param configurationMap Map of configKey to WifiConfiguration object.
*/
private void loadFromIpConfigStore(Map<String, WifiConfiguration> configurationMap) {
// This is a map of the hash code of the network's configKey to the corresponding
// IpConfiguration.
SparseArray<IpConfiguration> ipConfigurations =
mIpconfigStoreWrapper.readIpAndProxyConfigurations(
IP_CONFIG_FILE.getAbsolutePath());
if (ipConfigurations == null || ipConfigurations.size() == 0) {
Log.w(TAG, "No ip configurations found in ipconfig store");
return;
}
for (int i = 0; i < ipConfigurations.size(); i++) {
int id = ipConfigurations.keyAt(i);
WifiConfiguration config =
lookupWifiConfigurationUsingConfigKeyHash(configurationMap, id);
// This is the only place the map is looked up through a (dangerous) hash-value!
if (config == null || config.ephemeral) {
Log.w(TAG, "configuration found for missing network, nid=" + id
+ ", ignored, networks.size=" + Integer.toString(ipConfigurations.size()));
} else {
config.setIpConfiguration(ipConfigurations.valueAt(i));
}
}
}
/**
* Helper function to load {@link WifiConfiguration} data from networkHistory file and populate
* the provided configuration map and deleted ephemeral ssid list.
*
* @param configurationMap Map of configKey to WifiConfiguration object.
* @param deletedEphemeralSSIDs Map of configKey to WifiConfiguration object.
*/
private void loadFromNetworkHistory(
Map<String, WifiConfiguration> configurationMap, Set<String> deletedEphemeralSSIDs) {
// TODO: Need to revisit the scan detail cache persistance. We're not doing it in the new
// config store, so ignore it here as well.
Map<Integer, ScanDetailCache> scanDetailCaches = new HashMap<>();
mWifiNetworkHistory.readNetworkHistory(
configurationMap, scanDetailCaches, deletedEphemeralSSIDs);
}
/**
* Helper function to load {@link WifiConfiguration} data from wpa_supplicant and populate
* the provided configuration map and network extras.
*
* This method needs to manually parse the wpa_supplicant.conf file to retrieve some of the
* password fields like psk, wep_keys. password, etc.
*
* @param configurationMap Map of configKey to WifiConfiguration object.
* @param networkExtras Map of network extras parsed from wpa_supplicant.
*/
private void loadFromWpaSupplicant(
Map<String, WifiConfiguration> configurationMap,
SparseArray<Map<String, String>> networkExtras) {
if (!mWifiNative.migrateNetworksFromSupplicant(mWifiNative.getClientInterfaceName(),
configurationMap, networkExtras)) {
Log.wtf(TAG, "Failed to load wifi configurations from wpa_supplicant");
return;
}
if (configurationMap.isEmpty()) {
Log.w(TAG, "No wifi configurations found in wpa_supplicant");
return;
}
}
/**
* Helper function to update {@link WifiConfiguration} that represents a Passpoint
* configuration.
*
* This method will manually parse PerProviderSubscription.conf file to retrieve missing
* fields: provider friendly name, roaming consortium OIs, realm, IMSI.
*
* @param configurationMap Map of configKey to WifiConfiguration object.
* @param networkExtras Map of network extras parsed from wpa_supplicant.
*/
private void loadFromPasspointConfigStore(
Map<String, WifiConfiguration> configurationMap,
SparseArray<Map<String, String>> networkExtras) {
Map<String, LegacyPasspointConfig> passpointConfigMap = null;
try {
passpointConfigMap = mPasspointConfigParser.parseConfig(PPS_FILE.getAbsolutePath());
} catch (IOException e) {
Log.w(TAG, "Failed to read/parse Passpoint config file: " + e.getMessage());
}
List<String> entriesToBeRemoved = new ArrayList<>();
for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
WifiConfiguration wifiConfig = entry.getValue();
// Ignore non-Enterprise network since enterprise configuration is required for
// Passpoint.
if (wifiConfig.enterpriseConfig == null || wifiConfig.enterpriseConfig.getEapMethod()
== WifiEnterpriseConfig.Eap.NONE) {
continue;
}
// Ignore configuration without FQDN.
Map<String, String> extras = networkExtras.get(wifiConfig.networkId);
if (extras == null || !extras.containsKey(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN)) {
continue;
}
String fqdn = networkExtras.get(wifiConfig.networkId).get(
SupplicantStaNetworkHal.ID_STRING_KEY_FQDN);
// Remove the configuration if failed to find the matching configuration in the
// Passpoint configuration file.
if (passpointConfigMap == null || !passpointConfigMap.containsKey(fqdn)) {
entriesToBeRemoved.add(entry.getKey());
continue;
}
// Update the missing Passpoint configuration fields to this WifiConfiguration.
LegacyPasspointConfig passpointConfig = passpointConfigMap.get(fqdn);
wifiConfig.isLegacyPasspointConfig = true;
wifiConfig.FQDN = fqdn;
wifiConfig.providerFriendlyName = passpointConfig.mFriendlyName;
if (passpointConfig.mRoamingConsortiumOis != null) {
wifiConfig.roamingConsortiumIds = Arrays.copyOf(
passpointConfig.mRoamingConsortiumOis,
passpointConfig.mRoamingConsortiumOis.length);
}
if (passpointConfig.mImsi != null) {
wifiConfig.enterpriseConfig.setPlmn(passpointConfig.mImsi);
}
if (passpointConfig.mRealm != null) {
wifiConfig.enterpriseConfig.setRealm(passpointConfig.mRealm);
}
}
// Remove any incomplete Passpoint configurations. Should never happen, in case it does
// remove them to avoid maintaining any invalid Passpoint configurations.
for (String key : entriesToBeRemoved) {
Log.w(TAG, "Remove incomplete Passpoint configuration: " + key);
configurationMap.remove(key);
}
}
/**
* Helper function to load from the different legacy stores:
* 1. Read the network configurations from wpa_supplicant using {@link WifiNative}.
* 2. Read the network configurations from networkHistory.txt using {@link WifiNetworkHistory}.
* 3. Read the Ip configurations from ipconfig.txt using {@link IpConfigStore}.
* 4. Read all the passpoint info from PerProviderSubscription.conf using
* {@link LegacyPasspointConfigParser}.
*/
public WifiConfigStoreDataLegacy read() {
final Map<String, WifiConfiguration> configurationMap = new HashMap<>();
final SparseArray<Map<String, String>> networkExtras = new SparseArray<>();
final Set<String> deletedEphemeralSSIDs = new HashSet<>();
loadFromWpaSupplicant(configurationMap, networkExtras);
loadFromNetworkHistory(configurationMap, deletedEphemeralSSIDs);
loadFromIpConfigStore(configurationMap);
loadFromPasspointConfigStore(configurationMap, networkExtras);
// Now create config store data instance to be returned.
return new WifiConfigStoreDataLegacy(
new ArrayList<>(configurationMap.values()), deletedEphemeralSSIDs);
}
/**
* Function to check if the legacy store files are present and hence load from those stores and
* then delete them.
*
* @return true if legacy store files are present, false otherwise.
*/
public boolean areStoresPresent() {
// We may have to keep the wpa_supplicant.conf file around. So, just use networkhistory.txt
// as a check to see if we have not yet migrated or not. This should be the last file
// that is deleted after migration.
File file = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
return file.exists();
}
/**
* Method to remove all the legacy store files. This should only be invoked once all
* the data has been migrated to the new store file.
* 1. Removes all networks from wpa_supplicant and saves it to wpa_supplicant.conf
* 2. Deletes ipconfig.txt
* 3. Deletes networkHistory.txt
*
* @return true if all the store files were deleted successfully, false otherwise.
*/
public boolean removeStores() {
// TODO(b/29352330): Delete wpa_supplicant.conf file instead.
// First remove all networks from wpa_supplicant and save configuration.
if (!mWifiNative.removeAllNetworks(mWifiNative.getClientInterfaceName())) {
Log.e(TAG, "Removing networks from wpa_supplicant failed");
}
// Now remove the ipconfig.txt file.
if (!IP_CONFIG_FILE.delete()) {
Log.e(TAG, "Removing ipconfig.txt failed");
}
// Now finally remove network history.txt
if (!NETWORK_HISTORY_FILE.delete()) {
Log.e(TAG, "Removing networkHistory.txt failed");
}
if (!PPS_FILE.delete()) {
Log.e(TAG, "Removing PerProviderSubscription.conf failed");
}
Log.i(TAG, "All legacy stores removed!");
return true;
}
/**
* Interface used to set a masked value in the provided configuration. The masked value is
* retrieved by parsing the wpa_supplicant.conf file.
*/
private interface MaskedWpaSupplicantFieldSetter {
void setValue(WifiConfiguration config, String value);
}
/**
* Class used to encapsulate all the store data retrieved from the legacy (Pre O) store files.
*/
public static class WifiConfigStoreDataLegacy {
private List<WifiConfiguration> mConfigurations;
private Set<String> mDeletedEphemeralSSIDs;
// private List<HomeSP> mHomeSps;
WifiConfigStoreDataLegacy(List<WifiConfiguration> configurations,
Set<String> deletedEphemeralSSIDs) {
mConfigurations = configurations;
mDeletedEphemeralSSIDs = deletedEphemeralSSIDs;
}
public List<WifiConfiguration> getConfigurations() {
return mConfigurations;
}
public Set<String> getDeletedEphemeralSSIDs() {
return mDeletedEphemeralSSIDs;
}
}
}