diff options
author | Roshan Pius <rpius@google.com> | 2016-06-07 20:36:31 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2016-06-07 20:36:31 +0000 |
commit | 0b476346f4bfa5879d7287d7b215e0c351d9a6ab (patch) | |
tree | c07e946af79ce77e6738238fa30515255a10a978 /service | |
parent | 0eeeac78a462f1cc1eaf38bf275a52e259170b9f (diff) | |
parent | e3831b70d4a8a967fe8df5496d542a432692c434 (diff) |
Merge "WifiBackupRestore: Parse older backup data"
Diffstat (limited to 'service')
3 files changed, 385 insertions, 8 deletions
diff --git a/service/java/com/android/server/wifi/WifiBackupRestore.java b/service/java/com/android/server/wifi/WifiBackupRestore.java index 012d95470..a3bde7b37 100644 --- a/service/java/com/android/server/wifi/WifiBackupRestore.java +++ b/service/java/com/android/server/wifi/WifiBackupRestore.java @@ -23,18 +23,24 @@ import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiEnterpriseConfig; import android.util.Log; +import android.util.SparseArray; import android.util.Xml; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; +import com.android.server.net.IpConfigStore; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.CharArrayReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.Inet4Address; @@ -42,7 +48,9 @@ import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.BitSet; +import java.util.HashSet; import java.util.List; +import java.util.Map; import static android.net.IpConfiguration.IpAssignment; import static android.net.IpConfiguration.ProxySettings; @@ -423,8 +431,8 @@ public class WifiBackupRestore { // configKey stored in the XML data. String configKeyCalculated = configuration.configKey(); if (!configKeyInData.equals(configKeyCalculated)) { - Log.e(TAG, "Configuration key does not match. InData: " + configKeyInData - + "Calculated: " + configKeyCalculated); + Log.e(TAG, "Configuration key does not match. Retrieved: " + configKeyInData + + ", Calculated: " + configKeyCalculated); return null; } // Now retrieve any IP configuration info if present. @@ -566,4 +574,323 @@ public class WifiBackupRestore { } Log.d(TAG, logString + ": " + xmlString); } + + /** + * Restore state from the older supplicant back up data. + * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file. + * + * @param supplicantData Raw byte stream of wpa_supplicant.conf + * @param ipConfigData Raw byte stream of ipconfig.txt + * @return list of networks retrieved from the backed up data. + */ + public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData( + byte[] supplicantData, byte[] ipConfigData) { + if (supplicantData == null || ipConfigData == null + || supplicantData.length == 0 || ipConfigData.length == 0) { + Log.e(TAG, "Invalid supplicant backup data received"); + return null; + } + + SupplicantBackupMigration.SupplicantNetworks supplicantNetworks = + new SupplicantBackupMigration.SupplicantNetworks(); + // Incorporate the networks present in the backup data. + char[] restoredAsBytes = new char[supplicantData.length]; + for (int i = 0; i < supplicantData.length; i++) { + restoredAsBytes[i] = (char) supplicantData[i]; + } + + BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsBytes)); + supplicantNetworks.readNetworksFromStream(in); + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Final network list from old backup:"); + supplicantNetworks.dump(); + } + + // Retrieve corresponding WifiConfiguration objects. + List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations(); + + // Now retrieve all the IpConfiguration objects and set in the corresponding + // WifiConfiguration objects. + SparseArray<IpConfiguration> networks = + IpConfigStore.readIpAndProxyConfigurations(new ByteArrayInputStream(ipConfigData)); + if (networks != null) { + for (int i = 0; i < networks.size(); i++) { + int id = networks.keyAt(i); + for (WifiConfiguration configuration : configurations) { + // This is a dangerous lookup, but that's how it is currently written. + if (configuration.configKey().hashCode() == id) { + configuration.setIpConfiguration(networks.valueAt(i)); + } + } + } + } else { + Log.e(TAG, "Invalid Ip config data"); + } + return configurations; + } + + /** + * These sub classes contain the logic to parse older backups and restore wifi state from it. + * Most of the code here has been migrated over from BackupSettingsAgent. + * This is kind of ugly text parsing, but it is needed to support the migration of this data. + */ + public static class SupplicantBackupMigration { + /** + * List of keys to look out for in wpa_supplicant.conf parsing. + * These key values are declared in different parts of the wifi codebase today. + */ + @VisibleForTesting + public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName; + public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName; + public static final String SUPPLICANT_KEY_CLIENT_CERT = WifiEnterpriseConfig.CLIENT_CERT_KEY; + public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY; + public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY; + public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY; + public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName; + public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0]; + public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1]; + public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2]; + public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3]; + public static final String SUPPLICANT_KEY_WEP_KEY_IDX = WifiConfiguration.wepTxKeyIdxVarName; + public static final String SUPPLICANT_KEY_ID_STR = WifiConfigStore.ID_STRING_VAR_NAME; + + /** + * Class for capturing a network definition from the wifi supplicant config file. + */ + static class SupplicantNetwork { + final ArrayList<String> rawLines = new ArrayList<String>(); + String ssid; + String key_mgmt; + String psk; + String[] wepKeys = new String[4]; + String wepTxKeyIdx; + String id_str; + boolean certUsed = false; + boolean isEap = false; + + /** + * Read lines from wpa_supplicant.conf stream for this network. + */ + public static SupplicantNetwork readNetworkFromStream(BufferedReader in) { + final SupplicantNetwork n = new SupplicantNetwork(); + String line; + try { + while (in.ready()) { + line = in.readLine(); + if (line == null || line.startsWith("}")) { + break; + } + n.parseLine(line); + } + } catch (IOException e) { + return null; + } + return n; + } + + /** + * Parse a line from wpa_supplicant.conf stream for this network. + */ + void parseLine(String line) { + // Can't rely on particular whitespace patterns so strip leading/trailing. + line = line.trim(); + if (line.isEmpty()) return; // only whitespace; drop the line. + rawLines.add(line); + + // Now parse the network block within wpa_supplicant.conf and store the important + // lines for procesing later. + if (line.startsWith(SUPPLICANT_KEY_SSID)) { + ssid = line; + } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT)) { + key_mgmt = line; + if (line.contains("EAP")) { + isEap = true; + } + } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT)) { + certUsed = true; + } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT)) { + certUsed = true; + } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH)) { + certUsed = true; + } else if (line.startsWith(SUPPLICANT_KEY_EAP)) { + isEap = true; + } else if (line.startsWith(SUPPLICANT_KEY_PSK)) { + psk = line; + } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0)) { + wepKeys[0] = line; + } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1)) { + wepKeys[1] = line; + } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2)) { + wepKeys[2] = line; + } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3)) { + wepKeys[3] = line; + } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX)) { + wepTxKeyIdx = line; + } else if (line.startsWith(SUPPLICANT_KEY_ID_STR)) { + id_str = line; + } + } + + /** + * Raw dump of wpa_supplicant.conf lines. + */ + public void dump() { + Log.v(TAG, "network={"); + for (String line : rawLines) { + Log.v(TAG, " " + line); + } + Log.v(TAG, "}"); + } + + /** + * Create WifiConfiguration object from the parsed data for this network. + */ + public WifiConfiguration createWifiConfiguration() { + if (ssid == null) { + // No SSID => malformed network definition + return null; + } + WifiConfiguration configuration = new WifiConfiguration(); + configuration.SSID = ssid.substring(ssid.indexOf('=') + 1); + + if (key_mgmt == null) { + // no key_mgmt specified; this is defined as equivalent to "WPA-PSK WPA-EAP" + configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); + } else { + // Need to parse the key_mgmt line + final String bareKeyMgmt = key_mgmt.substring(key_mgmt.indexOf('=') + 1); + String[] typeStrings = bareKeyMgmt.split("\\s+"); + + // Parse out all the key management regimes permitted for this network. + // The literal strings here are the standard values permitted in + // wpa_supplicant.conf. + for (int i = 0; i < typeStrings.length; i++) { + final String ktype = typeStrings[i]; + if (ktype.equals("NONE")) { + configuration.allowedKeyManagement.set( + WifiConfiguration.KeyMgmt.NONE); + } else if (ktype.equals("WPA-PSK")) { + configuration.allowedKeyManagement.set( + WifiConfiguration.KeyMgmt.WPA_PSK); + } else if (ktype.equals("WPA-EAP")) { + configuration.allowedKeyManagement.set( + WifiConfiguration.KeyMgmt.WPA_EAP); + } else if (ktype.equals("IEEE8021X")) { + configuration.allowedKeyManagement.set( + WifiConfiguration.KeyMgmt.IEEE8021X); + } + } + } + if (psk != null) { + configuration.preSharedKey = psk.substring(psk.indexOf('=') + 1); + } + if (wepKeys[0] != null) { + configuration.wepKeys[0] = wepKeys[0].substring(wepKeys[0].indexOf('=') + 1); + } + if (wepKeys[1] != null) { + configuration.wepKeys[1] = wepKeys[1].substring(wepKeys[1].indexOf('=') + 1); + } + if (wepKeys[2] != null) { + configuration.wepKeys[2] = wepKeys[2].substring(wepKeys[2].indexOf('=') + 1); + } + if (wepKeys[3] != null) { + configuration.wepKeys[3] = wepKeys[3].substring(wepKeys[3].indexOf('=') + 1); + } + if (wepTxKeyIdx != null) { + configuration.wepTxKeyIndex = + Integer.valueOf(wepTxKeyIdx.substring(wepTxKeyIdx.indexOf('=') + 1)); + } + if (id_str != null) { + String id_string = id_str.substring(id_str.indexOf('=') + 1); + Map<String, String> extras = WifiNative.parseNetworkExtra(id_string); + configuration.creatorUid = + Integer.valueOf(extras.get(WifiConfigStore.ID_STRING_KEY_CREATOR_UID)); + String configKey = extras.get(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY); + if (!configKey.equals(configuration.configKey())) { + Log.e(TAG, "Configuration key does not match. Retrieved: " + configKey + + ", Calculated: " + configuration.configKey()); + return null; + } + } + return configuration; + } + } + + /** + * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={} + * blocks and eliminating duplicates + */ + static class SupplicantNetworks { + // One for fast lookup, one for maintaining ordering + final HashSet<SupplicantNetwork> mKnownNetworks = new HashSet<>(); + final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8); + + /** + * Parse the wpa_supplicant.conf file stream and add networks. + */ + public void readNetworksFromStream(BufferedReader in) { + try { + String line; + while (in.ready()) { + line = in.readLine(); + if (line != null) { + // Parse out 'network=' decls so we can ignore duplicates + if (line.startsWith("network")) { + SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in); + // Don't propagate EAP network definitions + if (net.isEap) { + Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + + net.key_mgmt); + continue; + } + Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt); + mKnownNetworks.add(net); + mNetworks.add(net); + } + } + } + } catch (IOException e) { + // whatever happened, we're done now + } + } + + /** + * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf + * + * @return + */ + public List<WifiConfiguration> retrieveWifiConfigurations() { + ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>(); + for (SupplicantNetwork net : mNetworks) { + if (net.certUsed) { + // Networks that use certificates for authentication can't be restored + // because the certificates they need don't get restored (because they + // are stored in keystore, and can't be restored) + continue; + } + + if (net.isEap) { + // Similarly, omit EAP network definitions to avoid propagating + // controlled enterprise network definitions. + continue; + } + WifiConfiguration wifiConfiguration = net.createWifiConfiguration(); + if (wifiConfiguration != null) { + wifiConfigurations.add(wifiConfiguration); + } + } + return wifiConfigurations; + } + + /** + * Raw dump of wpa_supplicant.conf lines. + */ + public void dump() { + for (SupplicantNetwork net : mNetworks) { + net.dump(); + } + } + } + } } diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java index 9828e6610..68397b774 100644 --- a/service/java/com/android/server/wifi/WifiNative.java +++ b/service/java/com/android/server/wifi/WifiNative.java @@ -45,6 +45,7 @@ import android.util.LocalLog; import android.util.Log; import com.android.internal.annotations.Immutable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.server.connectivity.KeepalivePacketData; import com.android.server.wifi.hotspot2.NetworkDetail; @@ -389,17 +390,26 @@ public class WifiNative { } public boolean setNetworkExtra(int netId, String name, Map<String, String> values) { + String encoded = createNetworkExtra(values); + if (encoded == null) { + return false; + } + return setNetworkVariable(netId, name, "\"" + encoded + "\""); + } + + @VisibleForTesting + public static String createNetworkExtra(Map<String, String> values) { final String encoded; try { encoded = URLEncoder.encode(new JSONObject(values).toString(), "UTF-8"); } catch (NullPointerException e) { Log.e(TAG, "Unable to serialize networkExtra: " + e.toString()); - return false; + return null; } catch (UnsupportedEncodingException e) { Log.e(TAG, "Unable to serialize networkExtra: " + e.toString()); - return false; + return null; } - return setNetworkVariable(netId, name, "\"" + encoded + "\""); + return encoded; } public boolean setNetworkVariable(int netId, String name, String value) { @@ -413,12 +423,16 @@ public class WifiNative { } public Map<String, String> getNetworkExtra(int netId, String name) { - final String wrapped = getNetworkVariable(netId, name); - if (wrapped == null || !wrapped.startsWith("\"") || !wrapped.endsWith("\"")) { + final String extraString = getNetworkVariable(netId, name); + return parseNetworkExtra(extraString); + } + + public static Map<String, String> parseNetworkExtra(String extraSting) { + if (extraSting == null || !extraSting.startsWith("\"") || !extraSting.endsWith("\"")) { return null; } try { - final String encoded = wrapped.substring(1, wrapped.length() - 1); + final String encoded = extraSting.substring(1, extraSting.length() - 1); // This method reads a JSON dictionary that was written by setNetworkExtra(). However, // on devices that upgraded from Marshmallow, it may encounter a legacy value instead - // an FQDN stored as a plain string. If such a value is encountered, the JSONObject diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 3f8c26985..34c7438f9 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -1974,6 +1974,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { /** * Restore state from the backed up data. + * * @param data Raw byte stream of the backed up data. */ @Override @@ -2002,4 +2003,39 @@ public class WifiServiceImpl extends IWifiManager.Stub { mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, networkId, false); } } + + /** + * Restore state from the older supplicant back up data. + * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file. + * + * @param supplicantData Raw byte stream of wpa_supplicant.conf + * @param ipConfigData Raw byte stream of ipconfig.txt + * @hide + */ + public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) { + enforceChangePermission(); + if (mWifiStateMachineChannel == null) { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return; + } + + Slog.d(TAG, "Restore supplicant backup data"); + List<WifiConfiguration> wifiConfigurations = + mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData( + supplicantData, ipConfigData); + if (wifiConfigurations == null) { + Slog.e(TAG, "Backup data parse failed"); + return; + } + + for (WifiConfiguration configuration : wifiConfigurations) { + int networkId = + mWifiStateMachine.syncAddOrUpdateNetwork( + mWifiStateMachineChannel, configuration); + if (networkId == WifiConfiguration.INVALID_NETWORK_ID) { + Slog.e(TAG, "Restore network failed: " + configuration.configKey()); + } + mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, networkId, false); + } + } } |