diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java index 6b623f48ff..6006539600 100644 --- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java +++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java @@ -16,23 +16,37 @@ package com.android.server.ethernet; +import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME; + import android.annotation.Nullable; +import android.content.ApexEnvironment; import android.net.IpConfiguration; import android.os.Environment; import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.net.IpConfigStore; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; /** * This class provides an API to store and manage Ethernet network configuration. */ public class EthernetConfigStore { - private static final String ipConfigFile = Environment.getDataDirectory() + - "/misc/ethernet/ipconfig.txt"; + private static final String TAG = EthernetConfigStore.class.getSimpleName(); + private static final String CONFIG_FILE = "ipconfig.txt"; + private static final String FILE_PATH = "/misc/ethernet/"; + private static final String LEGACY_IP_CONFIG_FILE_PATH = Environment.getDataDirectory() + + FILE_PATH; + private static final String APEX_IP_CONFIG_FILE_PATH = ApexEnvironment.getApexEnvironment( + TETHERING_MODULE_NAME).getDeviceProtectedDataDir() + FILE_PATH; private IpConfigStore mStore = new IpConfigStore(); - private ArrayMap mIpConfigurations; + private final ArrayMap mIpConfigurations; private IpConfiguration mIpConfigurationForDefaultInterface; private final Object mSync = new Object(); @@ -40,22 +54,70 @@ public class EthernetConfigStore { mIpConfigurations = new ArrayMap<>(0); } - public void read() { - synchronized (mSync) { - ArrayMap configs = - IpConfigStore.readIpConfigurations(ipConfigFile); + private static boolean doesConfigFileExist(final String filepath) { + return new File(filepath).exists(); + } - // This configuration may exist in old file versions when there was only a single active - // Ethernet interface. - if (configs.containsKey("0")) { - mIpConfigurationForDefaultInterface = configs.remove("0"); + private void writeLegacyIpConfigToApexPath(final String newFilePath, final String oldFilePath, + final String filename) { + final File directory = new File(newFilePath); + if (!directory.exists()) { + directory.mkdirs(); + } + + // Write the legacy IP config to the apex file path. + FileOutputStream fos = null; + final AtomicFile dst = new AtomicFile(new File(newFilePath + filename)); + final AtomicFile src = new AtomicFile(new File(oldFilePath + filename)); + try { + final byte[] raw = src.readFully(); + if (raw.length > 0) { + fos = dst.startWrite(); + fos.write(raw); + fos.flush(); + dst.finishWrite(fos); } - - mIpConfigurations = configs; + } catch (IOException e) { + Log.e(TAG, "Fail to sync the legacy IP config to the apex file path."); + dst.failWrite(fos); } } + public void read() { + read(APEX_IP_CONFIG_FILE_PATH, LEGACY_IP_CONFIG_FILE_PATH, CONFIG_FILE); + } + + @VisibleForTesting + void read(final String newFilePath, final String oldFilePath, final String filename) { + synchronized (mSync) { + // Attempt to read the IP configuration from apex file path first. + if (doesConfigFileExist(newFilePath + filename)) { + loadConfigFileLocked(newFilePath + filename); + return; + } + + // If the config file doesn't exist in the apex file path, attempt to read it from + // the legacy file path, if config file exists, write the legacy IP configuration to + // apex config file path, this should just happen on the first boot. New or updated + // config entries are only written to the apex config file later. + if (!doesConfigFileExist(oldFilePath + filename)) return; + loadConfigFileLocked(oldFilePath + filename); + writeLegacyIpConfigToApexPath(newFilePath, oldFilePath, filename); + } + } + + private void loadConfigFileLocked(final String filepath) { + final ArrayMap configs = + IpConfigStore.readIpConfigurations(filepath); + mIpConfigurations.putAll(configs); + } + public void write(String iface, IpConfiguration config) { + write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE); + } + + @VisibleForTesting + void write(String iface, IpConfiguration config, String filepath) { boolean modified; synchronized (mSync) { @@ -67,7 +129,7 @@ public class EthernetConfigStore { } if (modified) { - mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations); + mStore.writeIpConfigurations(filepath, mIpConfigurations); } } } @@ -80,9 +142,6 @@ public class EthernetConfigStore { @Nullable public IpConfiguration getIpConfigurationForDefaultInterface() { - synchronized (mSync) { - return mIpConfigurationForDefaultInterface == null - ? null : new IpConfiguration(mIpConfigurationForDefaultInterface); - } + return null; } } diff --git a/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java new file mode 100644 index 0000000000..a9f80ea436 --- /dev/null +++ b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2022 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.ethernet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.net.InetAddresses; +import android.net.IpConfiguration; +import android.net.IpConfiguration.IpAssignment; +import android.net.IpConfiguration.ProxySettings; +import android.net.LinkAddress; +import android.net.ProxyInfo; +import android.net.StaticIpConfiguration; +import android.util.ArrayMap; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class EthernetConfigStoreTest { + private static final LinkAddress LINKADDR = new LinkAddress("192.168.1.100/25"); + private static final InetAddress GATEWAY = InetAddresses.parseNumericAddress("192.168.1.1"); + private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("8.8.8.8"); + private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("8.8.4.4"); + private static final StaticIpConfiguration STATIC_IP_CONFIG = + new StaticIpConfiguration.Builder() + .setIpAddress(LINKADDR) + .setGateway(GATEWAY) + .setDnsServers(new ArrayList( + List.of(DNS1, DNS2))) + .build(); + private static final ProxyInfo PROXY_INFO = ProxyInfo.buildDirectProxy("test", 8888); + private static final IpConfiguration APEX_IP_CONFIG = + new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null); + private static final IpConfiguration LEGACY_IP_CONFIG = + new IpConfiguration(IpAssignment.STATIC, ProxySettings.STATIC, STATIC_IP_CONFIG, + PROXY_INFO); + + private EthernetConfigStore mEthernetConfigStore; + private File mApexTestDir; + private File mLegacyTestDir; + private File mApexConfigFile; + private File mLegacyConfigFile; + + private void createTestDir() { + final Context context = InstrumentationRegistry.getContext(); + final File baseDir = context.getFilesDir(); + mApexTestDir = new File(baseDir.getPath() + "/apex"); + mApexTestDir.mkdirs(); + + mLegacyTestDir = new File(baseDir.getPath() + "/legacy"); + mLegacyTestDir.mkdirs(); + } + + @Before + public void setUp() { + createTestDir(); + mEthernetConfigStore = new EthernetConfigStore(); + } + + @After + public void tearDown() { + mApexTestDir.delete(); + mLegacyTestDir.delete(); + } + + private void assertConfigFileExist(final String filepath) { + assertTrue(new File(filepath).exists()); + } + + /** Wait for the delayed write operation completes. */ + private void waitForMs(long ms) { + try { + Thread.sleep(ms); + } catch (final InterruptedException e) { + fail("Thread was interrupted"); + } + } + + @Test + public void testWriteIpConfigToApexFilePathAndRead() throws Exception { + // Write the config file to the apex file path, pretend the config file exits and + // check if IP config should be read from apex file path. + mApexConfigFile = new File(mApexTestDir.getPath(), "test.txt"); + mEthernetConfigStore.write("eth0", APEX_IP_CONFIG, mApexConfigFile.getPath()); + waitForMs(50); + + mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt"); + final ArrayMap ipConfigurations = + mEthernetConfigStore.getIpConfigurations(); + assertEquals(APEX_IP_CONFIG, ipConfigurations.get("eth0")); + + mApexConfigFile.delete(); + } + + @Test + public void testWriteIpConfigToLegacyFilePathAndRead() throws Exception { + // Write the config file to the legacy file path, pretend the config file exits and + // check if IP config should be read from legacy file path. + mLegacyConfigFile = new File(mLegacyTestDir, "test.txt"); + mEthernetConfigStore.write("0", LEGACY_IP_CONFIG, mLegacyConfigFile.getPath()); + waitForMs(50); + + mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt"); + final ArrayMap ipConfigurations = + mEthernetConfigStore.getIpConfigurations(); + assertEquals(LEGACY_IP_CONFIG, ipConfigurations.get("0")); + + // Check the same config file in apex file path is created. + assertConfigFileExist(mApexTestDir.getPath() + "/test.txt"); + + final File apexConfigFile = new File(mApexTestDir.getPath() + "/test.txt"); + apexConfigFile.delete(); + mLegacyConfigFile.delete(); + } +}