From 50ec1f33c67f6145dc862539f1a9953869ebf425 Mon Sep 17 00:00:00 2001 From: Pavel Maltsev Date: Tue, 31 Oct 2017 15:34:16 -0700 Subject: [PATCH 1/2] Enable multiple active Ethernet interfaces - add Ethernet interface configurations to config.xml; no vendors can specify network capabilities (in particular they can mark network as restricted which make sense for embedded applications + static IP configuration) - extend EthernetManager to support multiple interfaces, use interface name as an identificator - extend IpConfigStore to store IP configuration based on string identifier (e.g. ethernet name) Test: runtest -x frameworks/base/services/tests/servicestests/ -c com.android.server.net.IpConfigStoreTest Change-Id: Ic1e70003f2380ca8edb4469d6b34e27c5e8cf059 --- core/java/android/net/EthernetManager.java | 49 ++++-- core/java/android/net/IEthernetManager.aidl | 7 +- .../android/net/IEthernetServiceListener.aidl | 2 +- .../com/android/server/net/IpConfigStore.java | 100 ++++++++++--- .../android/server/net/IpConfigStoreTest.java | 140 ++++++++++++++++++ 5 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java index 31a30968cb..ecccda588a 100644 --- a/core/java/android/net/EthernetManager.java +++ b/core/java/android/net/EthernetManager.java @@ -18,9 +18,6 @@ package android.net; import android.annotation.SystemService; import android.content.Context; -import android.net.IEthernetManager; -import android.net.IEthernetServiceListener; -import android.net.IpConfiguration; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -45,18 +42,18 @@ public class EthernetManager { if (msg.what == MSG_AVAILABILITY_CHANGED) { boolean isAvailable = (msg.arg1 == 1); for (Listener listener : mListeners) { - listener.onAvailabilityChanged(isAvailable); + listener.onAvailabilityChanged((String) msg.obj, isAvailable); } } } }; - private final ArrayList mListeners = new ArrayList(); + private final ArrayList mListeners = new ArrayList<>(); private final IEthernetServiceListener.Stub mServiceListener = new IEthernetServiceListener.Stub() { @Override - public void onAvailabilityChanged(boolean isAvailable) { + public void onAvailabilityChanged(String iface, boolean isAvailable) { mHandler.obtainMessage( - MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, null).sendToTarget(); + MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, iface).sendToTarget(); } }; @@ -66,9 +63,10 @@ public class EthernetManager { public interface Listener { /** * Called when Ethernet port's availability is changed. - * @param isAvailable {@code true} if one or more Ethernet port exists. + * @param iface Ethernet interface name + * @param isAvailable {@code true} if Ethernet port exists. */ - public void onAvailabilityChanged(boolean isAvailable); + void onAvailabilityChanged(String iface, boolean isAvailable); } /** @@ -86,9 +84,9 @@ public class EthernetManager { * Get Ethernet configuration. * @return the Ethernet Configuration, contained in {@link IpConfiguration}. */ - public IpConfiguration getConfiguration() { + public IpConfiguration getConfiguration(String iface) { try { - return mService.getConfiguration(); + return mService.getConfiguration(iface); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -97,21 +95,29 @@ public class EthernetManager { /** * Set Ethernet configuration. */ - public void setConfiguration(IpConfiguration config) { + public void setConfiguration(String iface, IpConfiguration config) { try { - mService.setConfiguration(config); + mService.setConfiguration(iface, config); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Indicates whether the system currently has one or more - * Ethernet interfaces. + * Indicates whether the system currently has one or more Ethernet interfaces. */ public boolean isAvailable() { + return getAvailableInterfaces().length > 0; + } + + /** + * Indicates whether the system has given interface. + * + * @param iface Ethernet interface name + */ + public boolean isAvailable(String iface) { try { - return mService.isAvailable(); + return mService.isAvailable(iface); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -136,6 +142,17 @@ public class EthernetManager { } } + /** + * Returns an array of available Ethernet interface names. + */ + public String[] getAvailableInterfaces() { + try { + return mService.getAvailableInterfaces(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + /** * Removes a listener. * @param listener A {@link Listener} to remove. diff --git a/core/java/android/net/IEthernetManager.aidl b/core/java/android/net/IEthernetManager.aidl index 7a92eb955a..94960b51d3 100644 --- a/core/java/android/net/IEthernetManager.aidl +++ b/core/java/android/net/IEthernetManager.aidl @@ -26,9 +26,10 @@ import android.net.IEthernetServiceListener; /** {@hide} */ interface IEthernetManager { - IpConfiguration getConfiguration(); - void setConfiguration(in IpConfiguration config); - boolean isAvailable(); + String[] getAvailableInterfaces(); + IpConfiguration getConfiguration(String iface); + void setConfiguration(String iface, in IpConfiguration config); + boolean isAvailable(String iface); void addListener(in IEthernetServiceListener listener); void removeListener(in IEthernetServiceListener listener); } diff --git a/core/java/android/net/IEthernetServiceListener.aidl b/core/java/android/net/IEthernetServiceListener.aidl index 356690e8f7..782fa19d9d 100644 --- a/core/java/android/net/IEthernetServiceListener.aidl +++ b/core/java/android/net/IEthernetServiceListener.aidl @@ -19,5 +19,5 @@ package android.net; /** @hide */ oneway interface IEthernetServiceListener { - void onAvailabilityChanged(boolean isAvailable); + void onAvailabilityChanged(String iface, boolean isAvailable); } diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index 4d56468f2f..e3e02e32ad 100644 --- a/services/core/java/com/android/server/net/IpConfigStore.java +++ b/services/core/java/com/android/server/net/IpConfigStore.java @@ -24,11 +24,11 @@ import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.net.DelayedDiskWrite; import java.io.BufferedInputStream; import java.io.DataInputStream; @@ -38,8 +38,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.InetAddress; import java.net.Inet4Address; +import java.net.InetAddress; public class IpConfigStore { private static final String TAG = "IpConfigStore"; @@ -60,7 +60,7 @@ public class IpConfigStore { protected static final String EXCLUSION_LIST_KEY = "exclusionList"; protected static final String EOS = "eos"; - protected static final int IPCONFIG_FILE_VERSION = 2; + protected static final int IPCONFIG_FILE_VERSION = 3; public IpConfigStore(DelayedDiskWrite writer) { mWriter = writer; @@ -70,9 +70,14 @@ public class IpConfigStore { this(new DelayedDiskWrite()); } + private static boolean writeConfig(DataOutputStream out, String configKey, + IpConfiguration config) throws IOException { + return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION); + } + @VisibleForTesting - public static boolean writeConfig(DataOutputStream out, int configKey, - IpConfiguration config) throws IOException { + public static boolean writeConfig(DataOutputStream out, String configKey, + IpConfiguration config, int version) throws IOException { boolean written = false; try { @@ -153,7 +158,11 @@ public class IpConfigStore { if (written) { out.writeUTF(ID_KEY); - out.writeInt(configKey); + if (version < 3) { + out.writeInt(Integer.valueOf(configKey)); + } else { + out.writeUTF(configKey); + } } } catch (NullPointerException e) { loge("Failure in writing " + config + e); @@ -163,18 +172,47 @@ public class IpConfigStore { return written; } - public void writeIpAndProxyConfigurations(String filePath, + /** + * @Deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead. + * New method uses string as network identifier which could be interface name or MAC address or + * other token. + */ + @Deprecated + public void writeIpAndProxyConfigurationsToFile(String filePath, final SparseArray networks) { - mWriter.write(filePath, new DelayedDiskWrite.Writer() { - public void onWriteCalled(DataOutputStream out) throws IOException{ - out.writeInt(IPCONFIG_FILE_VERSION); - for(int i = 0; i < networks.size(); i++) { - writeConfig(out, networks.keyAt(i), networks.valueAt(i)); - } + mWriter.write(filePath, out -> { + out.writeInt(IPCONFIG_FILE_VERSION); + for(int i = 0; i < networks.size(); i++) { + writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i)); } }); } + public void writeIpConfigurations(String filePath, + ArrayMap networks) { + mWriter.write(filePath, out -> { + out.writeInt(IPCONFIG_FILE_VERSION); + for(int i = 0; i < networks.size(); i++) { + writeConfig(out, networks.keyAt(i), networks.valueAt(i)); + } + }); + } + + public static ArrayMap readIpConfigurations(String filePath) { + BufferedInputStream bufferedInputStream; + try { + bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath)); + } catch (FileNotFoundException e) { + // Return an empty array here because callers expect an empty array when the file is + // not present. + loge("Error opening configuration file: " + e); + return new ArrayMap<>(0); + } + return readIpConfigurations(bufferedInputStream); + } + + /** @Deprecated use {@link #readIpConfigurations(String)} */ + @Deprecated public static SparseArray readIpAndProxyConfigurations(String filePath) { BufferedInputStream bufferedInputStream; try { @@ -188,21 +226,40 @@ public class IpConfigStore { return readIpAndProxyConfigurations(bufferedInputStream); } + /** @Deprecated use {@link #readIpConfigurations(InputStream)} */ + @Deprecated public static SparseArray readIpAndProxyConfigurations( InputStream inputStream) { - SparseArray networks = new SparseArray(); + ArrayMap networks = readIpConfigurations(inputStream); + if (networks == null) { + return null; + } + + SparseArray networksById = new SparseArray<>(); + for (int i = 0; i < networks.size(); i++) { + int id = Integer.valueOf(networks.keyAt(i)); + networksById.put(id, networks.valueAt(i)); + } + + return networksById; + } + + /** Returns a map of network identity token and {@link IpConfiguration}. */ + public static ArrayMap readIpConfigurations( + InputStream inputStream) { + ArrayMap networks = new ArrayMap<>(); DataInputStream in = null; try { in = new DataInputStream(inputStream); int version = in.readInt(); - if (version != 2 && version != 1) { + if (version != 3 && version != 2 && version != 1) { loge("Bad version on IP configuration file, ignore read"); return null; } while (true) { - int id = -1; + String uniqueToken = null; // Default is DHCP with no proxy IpAssignment ipAssignment = IpAssignment.DHCP; ProxySettings proxySettings = ProxySettings.NONE; @@ -217,7 +274,12 @@ public class IpConfigStore { key = in.readUTF(); try { if (key.equals(ID_KEY)) { - id = in.readInt(); + if (version < 3) { + int id = in.readInt(); + uniqueToken = String.valueOf(id); + } else { + uniqueToken = in.readUTF(); + } } else if (key.equals(IP_ASSIGNMENT_KEY)) { ipAssignment = IpAssignment.valueOf(in.readUTF()); } else if (key.equals(LINK_ADDRESS_KEY)) { @@ -280,9 +342,9 @@ public class IpConfigStore { } } while (true); - if (id != -1) { + if (uniqueToken != null) { IpConfiguration config = new IpConfiguration(); - networks.put(id, config); + networks.put(uniqueToken, config); switch (ipAssignment) { case STATIC: diff --git a/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java b/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java new file mode 100644 index 0000000000..9f4b754621 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2018 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.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.net.IpConfiguration; +import android.net.IpConfiguration.IpAssignment; +import android.net.IpConfiguration.ProxySettings; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.net.ProxyInfo; +import android.net.StaticIpConfiguration; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Unit tests for {@link IpConfigStore} + */ +@RunWith(AndroidJUnit4.class) +public class IpConfigStoreTest { + + @Test + public void backwardCompatibility2to3() throws IOException { + final int KEY_CONFIG = 17; + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream outputStream = new DataOutputStream(byteStream); + + IpConfiguration expectedConfig = new IpConfiguration(IpAssignment.DHCP, + ProxySettings.NONE, null, null); + + // Emulate writing to old format. + writeDhcpConfigV2(outputStream, KEY_CONFIG, expectedConfig); + + InputStream in = new ByteArrayInputStream(byteStream.toByteArray()); + ArrayMap configurations = IpConfigStore.readIpConfigurations(in); + + assertNotNull(configurations); + assertEquals(1, configurations.size()); + IpConfiguration actualConfig = configurations.get(String.valueOf(KEY_CONFIG)); + assertNotNull(actualConfig); + assertEquals(expectedConfig, actualConfig); + } + + @Test + public void staticIpMultiNetworks() throws Exception { + final String IFACE_1 = "eth0"; + final String IFACE_2 = "eth1"; + final String IP_ADDR_1 = "192.168.1.10/24"; + final String IP_ADDR_2 = "192.168.1.20/24"; + final String DNS_IP_ADDR_1 = "1.2.3.4"; + final String DNS_IP_ADDR_2 = "5.6.7.8"; + + StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); + staticIpConfiguration.ipAddress = new LinkAddress(IP_ADDR_1); + staticIpConfiguration.dnsServers.add(NetworkUtils.numericToInetAddress(DNS_IP_ADDR_1)); + staticIpConfiguration.dnsServers.add(NetworkUtils.numericToInetAddress(DNS_IP_ADDR_2)); + + ProxyInfo proxyInfo = new ProxyInfo("10.10.10.10", 88, "host1,host2"); + + IpConfiguration expectedConfig1 = new IpConfiguration(IpAssignment.STATIC, + ProxySettings.STATIC, staticIpConfiguration, proxyInfo); + IpConfiguration expectedConfig2 = new IpConfiguration(expectedConfig1); + expectedConfig2.getStaticIpConfiguration().ipAddress = new LinkAddress(IP_ADDR_2); + + ArrayMap expectedNetworks = new ArrayMap<>(); + expectedNetworks.put(IFACE_1, expectedConfig1); + expectedNetworks.put(IFACE_2, expectedConfig2); + + MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite(); + IpConfigStore store = new IpConfigStore(writer); + store.writeIpConfigurations("file/path/not/used/", expectedNetworks); + + InputStream in = new ByteArrayInputStream(writer.byteStream.toByteArray()); + ArrayMap actualNetworks = IpConfigStore.readIpConfigurations(in); + assertNotNull(actualNetworks); + assertEquals(2, actualNetworks.size()); + assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1)); + assertEquals(expectedNetworks.get(IFACE_2), actualNetworks.get(IFACE_2)); + } + + // This is simplified snapshot of code that was used to store values in V2 format (key as int). + private static void writeDhcpConfigV2(DataOutputStream out, int configKey, + IpConfiguration config) throws IOException { + out.writeInt(2); // VERSION 2 + switch (config.ipAssignment) { + case DHCP: + out.writeUTF("ipAssignment"); + out.writeUTF(config.ipAssignment.toString()); + break; + default: + fail("Not supported in test environment"); + } + + out.writeUTF("id"); + out.writeInt(configKey); + out.writeUTF("eos"); + } + + /** Synchronously writes into given byte steam */ + private static class MockedDelayedDiskWrite extends DelayedDiskWrite { + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + + @Override + public void write(String filePath, Writer w) { + DataOutputStream outputStream = new DataOutputStream(byteStream); + + try { + w.onWriteCalled(outputStream); + } catch (IOException e) { + fail(); + } + } + } +} From 905f034c57310f1cfe2253a27dc38acff166dc24 Mon Sep 17 00:00:00 2001 From: Chenbo Feng Date: Thu, 25 Jan 2018 11:43:52 -0800 Subject: [PATCH 2/2] Return non-negetive value in getMobileStats method The current implementation of getMobileRxBytes and all the similiar method adds up the return values for multiple calls to getRxBytes so if all of them return UNSUPPORTED for any reason, getMobileRxBytes() would return a value such as -3. This behavior is not compliance with the cts TrafficStatsTest which always assume getMobileRxBytes to return a non-negetive value. The method now will check tha stats get from getRxBytes method and add them up only if the stats is valid. Bug: 72473294 Test: run cts -m CtsNetTestCases -t android.net.cts.TrafficStatsTest Change-Id: I656970ebc8f6506cf17c4353ad46c0178bb65cfd --- core/java/android/net/TrafficStats.java | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 196a3bc9c8..fa4624ef56 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -433,6 +433,10 @@ public class TrafficStats { } } + private static long addIfSupported(long stat) { + return (stat == UNSUPPORTED) ? 0 : stat; + } + /** * Return number of packets transmitted across mobile networks since device * boot. Counts packets across all mobile network interfaces, and always @@ -445,7 +449,7 @@ public class TrafficStats { public static long getMobileTxPackets() { long total = 0; for (String iface : getMobileIfaces()) { - total += getTxPackets(iface); + total += addIfSupported(getTxPackets(iface)); } return total; } @@ -462,7 +466,7 @@ public class TrafficStats { public static long getMobileRxPackets() { long total = 0; for (String iface : getMobileIfaces()) { - total += getRxPackets(iface); + total += addIfSupported(getRxPackets(iface)); } return total; } @@ -479,7 +483,7 @@ public class TrafficStats { public static long getMobileTxBytes() { long total = 0; for (String iface : getMobileIfaces()) { - total += getTxBytes(iface); + total += addIfSupported(getTxBytes(iface)); } return total; } @@ -496,7 +500,7 @@ public class TrafficStats { public static long getMobileRxBytes() { long total = 0; for (String iface : getMobileIfaces()) { - total += getRxBytes(iface); + total += addIfSupported(getRxBytes(iface)); } return total; } @@ -511,9 +515,7 @@ public class TrafficStats { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - if (stat != UNSUPPORTED) { - total += stat; - } + total += addIfSupported(stat); } return total; } @@ -528,9 +530,7 @@ public class TrafficStats { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - if (stat != UNSUPPORTED) { - total += stat; - } + total += addIfSupported(stat); } return total; }