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
This commit is contained in:
Pavel Maltsev
2017-10-31 15:34:16 -07:00
parent b559164017
commit 50ec1f33c6
5 changed files with 259 additions and 39 deletions

View File

@@ -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<Listener> mListeners = new ArrayList<Listener>();
private final ArrayList<Listener> 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.

View File

@@ -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);
}

View File

@@ -19,5 +19,5 @@ package android.net;
/** @hide */
oneway interface IEthernetServiceListener
{
void onAvailabilityChanged(boolean isAvailable);
void onAvailabilityChanged(String iface, boolean isAvailable);
}

View File

@@ -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<IpConfiguration> 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<String, IpConfiguration> 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<String, IpConfiguration> 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<IpConfiguration> 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<IpConfiguration> readIpAndProxyConfigurations(
InputStream inputStream) {
SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
ArrayMap<String, IpConfiguration> networks = readIpConfigurations(inputStream);
if (networks == null) {
return null;
}
SparseArray<IpConfiguration> 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<String, IpConfiguration> readIpConfigurations(
InputStream inputStream) {
ArrayMap<String, IpConfiguration> 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:

View File

@@ -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<String, IpConfiguration> 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<String, IpConfiguration> 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<String, IpConfiguration> 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();
}
}
}
}