Merge "Workaround prebuilt name conflict for service-connectivity" into tm-dev
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 44935fc..35a394d 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1288,7 +1288,7 @@
// Finally bring up serving on the new interface
mWifiP2pTetherInterface = group.getInterface();
- enableWifiIpServing(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+ enableWifiP2pIpServing(mWifiP2pTetherInterface);
}
private void handleUserRestrictionAction() {
@@ -1379,20 +1379,22 @@
changeInterfaceState(ifname, ipServingMode);
}
- private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
- mLog.log("Canceling WiFi tethering request -"
- + " type=" + tetheringType
- + " interface=" + ifname
- + " state=" + apState);
-
- if (!TextUtils.isEmpty(ifname)) {
- final TetherState ts = mTetherStates.get(ifname);
- if (ts != null) {
- ts.ipServer.unwanted();
- return;
- }
+ private void disableWifiIpServingCommon(int tetheringType, String ifname) {
+ if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
+ mTetherStates.get(ifname).ipServer.unwanted();
+ return;
}
+ if (SdkLevel.isAtLeastT()) {
+ mLog.e("Tethering no longer handle untracked interface after T: " + ifname);
+ return;
+ }
+
+ // Attempt to guess the interface name before T. Pure AOSP code should never enter here
+ // because WIFI_AP_STATE_CHANGED intent always include ifname and it should be tracked
+ // by mTetherStates. In case OEMs have some modification in wifi side which pass null
+ // or empty ifname. Before T, tethering allow to disable the first wifi ipServer if
+ // given ifname don't match any tracking ipServer.
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
@@ -1400,7 +1402,6 @@
return;
}
}
-
mLog.log("Error disabling Wi-Fi IP serving; "
+ (TextUtils.isEmpty(ifname) ? "no interface name specified"
: "specified interface: " + ifname));
@@ -1409,20 +1410,39 @@
private void disableWifiIpServing(String ifname, int apState) {
// Regardless of whether we requested this transition, the AP has gone
// down. Don't try to tether again unless we're requested to do so.
- // TODO: Remove this altogether, once Wi-Fi reliably gives us an
- // interface name with every broadcast.
mWifiTetherRequested = false;
- disableWifiIpServingCommon(TETHERING_WIFI, ifname, apState);
+ mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
+
+ disableWifiIpServingCommon(TETHERING_WIFI, ifname);
+ }
+
+ private void enableWifiP2pIpServing(String ifname) {
+ if (TextUtils.isEmpty(ifname)) {
+ mLog.e("Cannot enable P2P IP serving with invalid interface");
+ return;
+ }
+
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable p2p regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+ enableIpServing(type, ifname, IpServer.STATE_LOCAL_ONLY);
}
private void disableWifiP2pIpServingIfNeeded(String ifname) {
if (TextUtils.isEmpty(ifname)) return;
- disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
+ mLog.log("Canceling P2P tethering request - interface=" + ifname);
+ disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname);
}
private void enableWifiIpServing(String ifname, int wifiIpMode) {
+ mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
+
// Map wifiIpMode values to IpServer.Callback serving states, inferring
// from mWifiTetherRequested as a final "best guess".
final int ipServingMode;
@@ -1438,13 +1458,18 @@
return;
}
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable wifi regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+
if (!TextUtils.isEmpty(ifname)) {
- ensureIpServerStarted(ifname);
- changeInterfaceState(ifname, ipServingMode);
+ enableIpServing(type, ifname, ipServingMode);
} else {
- mLog.e(String.format(
- "Cannot enable IP serving in mode %s on missing interface name",
- ipServingMode));
+ mLog.e("Cannot enable IP serving on missing interface name");
}
}
@@ -2715,23 +2740,28 @@
mTetherMainSM.sendMessage(which, state, 0, newLp);
}
+ private boolean hasSystemFeature(final String feature) {
+ return mContext.getPackageManager().hasSystemFeature(feature);
+ }
+
+ private boolean checkTetherableType(int type) {
+ if ((type == TETHERING_WIFI || type == TETHERING_WIGIG)
+ && !hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ return false;
+ }
+
+ if (type == TETHERING_WIFI_P2P && !hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
+ return false;
+ }
+
+ return type != TETHERING_INVALID;
+ }
+
private void ensureIpServerStarted(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
- if (interfaceType == TETHERING_INVALID) {
- mLog.log(iface + " is not a tetherable iface, ignoring");
- return;
- }
-
- final PackageManager pm = mContext.getPackageManager();
- if ((interfaceType == TETHERING_WIFI || interfaceType == TETHERING_WIGIG)
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- mLog.log(iface + " is not tetherable, because WiFi feature is disabled");
- return;
- }
- if (interfaceType == TETHERING_WIFI_P2P
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
- mLog.log(iface + " is not tetherable, because WiFi Direct feature is disabled");
+ if (!checkTetherableType(interfaceType)) {
+ mLog.log(iface + " is used for " + interfaceType + " which is not tetherable");
return;
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 2fd7f48..6ef0e24 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -57,6 +57,7 @@
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -936,7 +937,7 @@
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1509,7 +1510,7 @@
// Emulate externally-visible WifiManager effects, when tethering mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1903,7 +1904,13 @@
mTethering.unregisterTetheringEventCallback(callback);
mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
+ if (isAtLeastT()) {
+ // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
+ callback2.assertNoStateChangeCallback();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ IFACE_IP_MODE_TETHERED);
+ }
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mLooper.dispatchAll();
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 6b623f4..6006539 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<String, IpConfiguration> mIpConfigurations;
+ private final ArrayMap<String, IpConfiguration> mIpConfigurations;
private IpConfiguration mIpConfigurationForDefaultInterface;
private final Object mSync = new Object();
@@ -40,22 +54,70 @@
mIpConfigurations = new ArrayMap<>(0);
}
- public void read() {
- synchronized (mSync) {
- ArrayMap<String, IpConfiguration> 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<String, IpConfiguration> 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 @@
}
if (modified) {
- mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
+ mStore.writeIpConfigurations(filepath, mIpConfigurations);
}
}
}
@@ -80,9 +142,6 @@
@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 0000000..a9f80ea
--- /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<InetAddress>(
+ 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<String, IpConfiguration> 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<String, IpConfiguration> 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();
+ }
+}