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:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -19,5 +19,5 @@ package android.net;
|
||||
/** @hide */
|
||||
oneway interface IEthernetServiceListener
|
||||
{
|
||||
void onAvailabilityChanged(boolean isAvailable);
|
||||
void onAvailabilityChanged(String iface, boolean isAvailable);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user