Merge history of opt/net/ethernet
Renamed files/directories: java/ --> service-t/src/ tests/ --> tests/ethernet/ Android.bp --> (removed) OWNERS --> (removed) .gitignore --> (removed) BUG: 222234190 TEST: TH Merged-In: I770bf8db3f4c18467934eb2184f5dc2408fc28ec Merged-In: I3e5df1bd44defbb9dd0c382c625a21e176368f2a Change-Id: Ifc02784499d114e12d8ad64ba011dd23b97fb78b
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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 android.annotation.Nullable;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
|
||||||
|
import com.android.server.net.IpConfigStore;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 IpConfigStore mStore = new IpConfigStore();
|
||||||
|
private ArrayMap<String, IpConfiguration> mIpConfigurations;
|
||||||
|
private IpConfiguration mIpConfigurationForDefaultInterface;
|
||||||
|
private final Object mSync = new Object();
|
||||||
|
|
||||||
|
public EthernetConfigStore() {
|
||||||
|
mIpConfigurations = new ArrayMap<>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void read() {
|
||||||
|
synchronized (mSync) {
|
||||||
|
ArrayMap<String, IpConfiguration> configs =
|
||||||
|
IpConfigStore.readIpConfigurations(ipConfigFile);
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
mIpConfigurations = configs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(String iface, IpConfiguration config) {
|
||||||
|
boolean modified;
|
||||||
|
|
||||||
|
synchronized (mSync) {
|
||||||
|
if (config == null) {
|
||||||
|
modified = mIpConfigurations.remove(iface) != null;
|
||||||
|
} else {
|
||||||
|
IpConfiguration oldConfig = mIpConfigurations.put(iface, config);
|
||||||
|
modified = !config.equals(oldConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayMap<String, IpConfiguration> getIpConfigurations() {
|
||||||
|
synchronized (mSync) {
|
||||||
|
return new ArrayMap<>(mIpConfigurations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public IpConfiguration getIpConfigurationForDefaultInterface() {
|
||||||
|
synchronized (mSync) {
|
||||||
|
return mIpConfigurationForDefaultInterface == null
|
||||||
|
? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 android.content.Context;
|
||||||
|
import android.net.LinkProperties;
|
||||||
|
import android.net.NetworkAgent;
|
||||||
|
import android.net.NetworkAgentConfig;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.net.NetworkProvider;
|
||||||
|
import android.net.NetworkScore;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
|
||||||
|
public class EthernetNetworkAgent extends NetworkAgent {
|
||||||
|
|
||||||
|
private static final String TAG = "EthernetNetworkAgent";
|
||||||
|
|
||||||
|
public interface Callbacks {
|
||||||
|
void onNetworkUnwanted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Callbacks mCallbacks;
|
||||||
|
|
||||||
|
EthernetNetworkAgent(
|
||||||
|
@NonNull Context context,
|
||||||
|
@NonNull Looper looper,
|
||||||
|
@NonNull NetworkCapabilities nc,
|
||||||
|
@NonNull LinkProperties lp,
|
||||||
|
@NonNull NetworkAgentConfig config,
|
||||||
|
@Nullable NetworkProvider provider,
|
||||||
|
@NonNull Callbacks cb) {
|
||||||
|
super(context, looper, TAG, nc, lp, new NetworkScore.Builder().build(), config, provider);
|
||||||
|
mCallbacks = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNetworkUnwanted() {
|
||||||
|
mCallbacks.onNetworkUnwanted();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendLinkProperties is final in NetworkAgent, so it cannot be mocked.
|
||||||
|
public void sendLinkPropertiesImpl(LinkProperties lp) {
|
||||||
|
sendLinkProperties(lp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callbacks getCallbacks() {
|
||||||
|
return mCallbacks;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,785 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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 android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.ConnectivityResources;
|
||||||
|
import android.net.EthernetManager;
|
||||||
|
import android.net.EthernetNetworkSpecifier;
|
||||||
|
import android.net.EthernetNetworkManagementException;
|
||||||
|
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.net.IpConfiguration.IpAssignment;
|
||||||
|
import android.net.IpConfiguration.ProxySettings;
|
||||||
|
import android.net.LinkProperties;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.NetworkAgentConfig;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.net.NetworkFactory;
|
||||||
|
import android.net.NetworkProvider;
|
||||||
|
import android.net.NetworkRequest;
|
||||||
|
import android.net.NetworkSpecifier;
|
||||||
|
import android.net.ip.IIpClient;
|
||||||
|
import android.net.ip.IpClientCallbacks;
|
||||||
|
import android.net.ip.IpClientManager;
|
||||||
|
import android.net.ip.IpClientUtil;
|
||||||
|
import android.net.shared.ProvisioningConfiguration;
|
||||||
|
import android.os.ConditionVariable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AndroidRuntimeException;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.android.connectivity.resources.R;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
import com.android.net.module.util.InterfaceParams;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link NetworkFactory} that represents Ethernet networks.
|
||||||
|
*
|
||||||
|
* This class reports a static network score of 70 when it is tracking an interface and that
|
||||||
|
* interface's link is up, and a score of 0 otherwise.
|
||||||
|
*/
|
||||||
|
public class EthernetNetworkFactory extends NetworkFactory {
|
||||||
|
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
|
||||||
|
final static boolean DBG = true;
|
||||||
|
|
||||||
|
private final static int NETWORK_SCORE = 70;
|
||||||
|
private static final String NETWORK_TYPE = "Ethernet";
|
||||||
|
private static final String LEGACY_TCP_BUFFER_SIZES =
|
||||||
|
"524288,1048576,3145728,524288,1048576,2097152";
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
private final Handler mHandler;
|
||||||
|
private final Context mContext;
|
||||||
|
final Dependencies mDeps;
|
||||||
|
|
||||||
|
public static class Dependencies {
|
||||||
|
public void makeIpClient(Context context, String iface, IpClientCallbacks callbacks) {
|
||||||
|
IpClientUtil.makeIpClient(context, iface, callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpClientManager makeIpClientManager(@NonNull final IIpClient ipClient) {
|
||||||
|
return new IpClientManager(ipClient, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EthernetNetworkAgent makeEthernetNetworkAgent(Context context, Looper looper,
|
||||||
|
NetworkCapabilities nc, LinkProperties lp, NetworkAgentConfig config,
|
||||||
|
NetworkProvider provider, EthernetNetworkAgent.Callbacks cb) {
|
||||||
|
return new EthernetNetworkAgent(context, looper, nc, lp, config, provider, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InterfaceParams getNetworkInterfaceByName(String name) {
|
||||||
|
return InterfaceParams.getByName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove legacy resource fallback after migrating its overlays.
|
||||||
|
private String getPlatformTcpBufferSizes(Context context) {
|
||||||
|
final Resources r = context.getResources();
|
||||||
|
final int resId = r.getIdentifier("config_ethernet_tcp_buffers", "string",
|
||||||
|
context.getPackageName());
|
||||||
|
return r.getString(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTcpBufferSizesFromResource(Context context) {
|
||||||
|
final String tcpBufferSizes;
|
||||||
|
final String platformTcpBufferSizes = getPlatformTcpBufferSizes(context);
|
||||||
|
if (!LEGACY_TCP_BUFFER_SIZES.equals(platformTcpBufferSizes)) {
|
||||||
|
// Platform resource is not the historical default: use the overlay.
|
||||||
|
tcpBufferSizes = platformTcpBufferSizes;
|
||||||
|
} else {
|
||||||
|
final ConnectivityResources resources = new ConnectivityResources(context);
|
||||||
|
tcpBufferSizes = resources.get().getString(R.string.config_ethernet_tcp_buffers);
|
||||||
|
}
|
||||||
|
return tcpBufferSizes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConfigurationException extends AndroidRuntimeException {
|
||||||
|
public ConfigurationException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EthernetNetworkFactory(Handler handler, Context context) {
|
||||||
|
this(handler, context, new Dependencies());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
EthernetNetworkFactory(Handler handler, Context context, Dependencies deps) {
|
||||||
|
super(handler.getLooper(), context, NETWORK_TYPE, createDefaultNetworkCapabilities());
|
||||||
|
|
||||||
|
mHandler = handler;
|
||||||
|
mContext = context;
|
||||||
|
mDeps = deps;
|
||||||
|
|
||||||
|
setScoreFilter(NETWORK_SCORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptRequest(NetworkRequest request) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, "acceptRequest, request: " + request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkForRequest(request) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void needNetworkFor(NetworkRequest networkRequest) {
|
||||||
|
NetworkInterfaceState network = networkForRequest(networkRequest);
|
||||||
|
|
||||||
|
if (network == null) {
|
||||||
|
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++network.refCount == 1) {
|
||||||
|
network.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void releaseNetworkFor(NetworkRequest networkRequest) {
|
||||||
|
NetworkInterfaceState network = networkForRequest(networkRequest);
|
||||||
|
if (network == null) {
|
||||||
|
Log.e(TAG, "releaseNetworkFor, failed to get a network for " + networkRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (--network.refCount == 0) {
|
||||||
|
network.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of available interface names. The array is sorted: unrestricted interfaces
|
||||||
|
* goes first, then sorted by name.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||||
|
protected String[] getAvailableInterfaces(boolean includeRestricted) {
|
||||||
|
return mTrackingInterfaces.values()
|
||||||
|
.stream()
|
||||||
|
.filter(iface -> !iface.isRestricted() || includeRestricted)
|
||||||
|
.sorted((iface1, iface2) -> {
|
||||||
|
int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
|
||||||
|
return r == 0 ? iface1.name.compareTo(iface2.name) : r;
|
||||||
|
})
|
||||||
|
.map(iface -> iface.name)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||||
|
protected void addInterface(@NonNull final String ifaceName, @NonNull final String hwAddress,
|
||||||
|
@NonNull final IpConfiguration ipConfig,
|
||||||
|
@NonNull final NetworkCapabilities capabilities) {
|
||||||
|
if (mTrackingInterfaces.containsKey(ifaceName)) {
|
||||||
|
Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final NetworkCapabilities nc = new NetworkCapabilities.Builder(capabilities)
|
||||||
|
.setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + nc);
|
||||||
|
}
|
||||||
|
|
||||||
|
final NetworkInterfaceState iface = new NetworkInterfaceState(
|
||||||
|
ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, this, mDeps);
|
||||||
|
mTrackingInterfaces.put(ifaceName, iface);
|
||||||
|
updateCapabilityFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected int getInterfaceState(@NonNull String iface) {
|
||||||
|
final NetworkInterfaceState interfaceState = mTrackingInterfaces.get(iface);
|
||||||
|
if (interfaceState == null) {
|
||||||
|
return EthernetManager.STATE_ABSENT;
|
||||||
|
} else if (!interfaceState.mLinkUp) {
|
||||||
|
return EthernetManager.STATE_LINK_DOWN;
|
||||||
|
} else {
|
||||||
|
return EthernetManager.STATE_LINK_UP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a network's configuration and restart it if necessary.
|
||||||
|
*
|
||||||
|
* @param ifaceName the interface name of the network to be updated.
|
||||||
|
* @param ipConfig the desired {@link IpConfiguration} for the given network or null. If
|
||||||
|
* {@code null} is passed, the existing IpConfiguration is not updated.
|
||||||
|
* @param capabilities the desired {@link NetworkCapabilities} for the given network. If
|
||||||
|
* {@code null} is passed, then the network's current
|
||||||
|
* {@link NetworkCapabilities} will be used in support of existing APIs as
|
||||||
|
* the public API does not allow this.
|
||||||
|
* @param listener an optional {@link INetworkInterfaceOutcomeReceiver} to notify callers of
|
||||||
|
* completion.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||||
|
protected void updateInterface(@NonNull final String ifaceName,
|
||||||
|
@Nullable final IpConfiguration ipConfig,
|
||||||
|
@Nullable final NetworkCapabilities capabilities,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (!hasInterface(ifaceName)) {
|
||||||
|
maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
|
||||||
|
iface.updateInterface(ipConfig, capabilities, listener);
|
||||||
|
mTrackingInterfaces.put(ifaceName, iface);
|
||||||
|
updateCapabilityFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
|
||||||
|
NetworkCapabilities addedNc) {
|
||||||
|
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(nc);
|
||||||
|
for (int transport : addedNc.getTransportTypes()) builder.addTransportType(transport);
|
||||||
|
for (int capability : addedNc.getCapabilities()) builder.addCapability(capability);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCapabilityFilter() {
|
||||||
|
NetworkCapabilities capabilitiesFilter = createDefaultNetworkCapabilities();
|
||||||
|
for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
|
||||||
|
capabilitiesFilter = mixInCapabilities(capabilitiesFilter, iface.mCapabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
|
||||||
|
setCapabilityFilter(capabilitiesFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkCapabilities createDefaultNetworkCapabilities() {
|
||||||
|
return NetworkCapabilities.Builder
|
||||||
|
.withoutDefaultCapabilities()
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||||
|
protected void removeInterface(String interfaceName) {
|
||||||
|
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
|
||||||
|
if (iface != null) {
|
||||||
|
iface.maybeSendNetworkManagementCallbackForAbort();
|
||||||
|
iface.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCapabilityFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if state has been modified */
|
||||||
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||||
|
protected boolean updateInterfaceLinkState(@NonNull final String ifaceName, final boolean up,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (!hasInterface(ifaceName)) {
|
||||||
|
maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
|
||||||
|
return iface.updateLinkState(up, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeSendNetworkManagementCallbackForUntracked(
|
||||||
|
String ifaceName, INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
maybeSendNetworkManagementCallback(listener, null,
|
||||||
|
new EthernetNetworkManagementException(
|
||||||
|
ifaceName + " can't be updated as it is not available."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected boolean hasInterface(String ifaceName) {
|
||||||
|
return mTrackingInterfaces.containsKey(ifaceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkInterfaceState networkForRequest(NetworkRequest request) {
|
||||||
|
String requestedIface = null;
|
||||||
|
|
||||||
|
NetworkSpecifier specifier = request.getNetworkSpecifier();
|
||||||
|
if (specifier instanceof EthernetNetworkSpecifier) {
|
||||||
|
requestedIface = ((EthernetNetworkSpecifier) specifier)
|
||||||
|
.getInterfaceName();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkInterfaceState network = null;
|
||||||
|
if (!TextUtils.isEmpty(requestedIface)) {
|
||||||
|
NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
|
||||||
|
if (n != null && request.canBeSatisfiedBy(n.mCapabilities)) {
|
||||||
|
network = n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
|
||||||
|
if (request.canBeSatisfiedBy(n.mCapabilities) && n.mLinkUp) {
|
||||||
|
network = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DBG) {
|
||||||
|
Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
|
||||||
|
}
|
||||||
|
|
||||||
|
return network;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void maybeSendNetworkManagementCallback(
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener,
|
||||||
|
@Nullable final String iface,
|
||||||
|
@Nullable final EthernetNetworkManagementException e) {
|
||||||
|
if (null == listener) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (iface != null) {
|
||||||
|
listener.onResult(iface);
|
||||||
|
} else {
|
||||||
|
listener.onError(e);
|
||||||
|
}
|
||||||
|
} catch (RemoteException re) {
|
||||||
|
Log.e(TAG, "Can't send onComplete for network management callback", re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static class NetworkInterfaceState {
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
private final String mHwAddress;
|
||||||
|
private final Handler mHandler;
|
||||||
|
private final Context mContext;
|
||||||
|
private final NetworkFactory mNetworkFactory;
|
||||||
|
private final Dependencies mDeps;
|
||||||
|
|
||||||
|
private static String sTcpBufferSizes = null; // Lazy initialized.
|
||||||
|
|
||||||
|
private boolean mLinkUp;
|
||||||
|
private int mLegacyType;
|
||||||
|
private LinkProperties mLinkProperties = new LinkProperties();
|
||||||
|
|
||||||
|
private volatile @Nullable IpClientManager mIpClient;
|
||||||
|
private @NonNull NetworkCapabilities mCapabilities;
|
||||||
|
private @Nullable EthernetIpClientCallback mIpClientCallback;
|
||||||
|
private @Nullable EthernetNetworkAgent mNetworkAgent;
|
||||||
|
private @Nullable IpConfiguration mIpConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of TRANSPORT_* types to legacy transport types available for each type an ethernet
|
||||||
|
* interface could propagate.
|
||||||
|
*
|
||||||
|
* There are no legacy type equivalents to LOWPAN or WIFI_AWARE. These types are set to
|
||||||
|
* TYPE_NONE to match the behavior of their own network factories.
|
||||||
|
*/
|
||||||
|
private static final SparseArray<Integer> sTransports = new SparseArray();
|
||||||
|
static {
|
||||||
|
sTransports.put(NetworkCapabilities.TRANSPORT_ETHERNET,
|
||||||
|
ConnectivityManager.TYPE_ETHERNET);
|
||||||
|
sTransports.put(NetworkCapabilities.TRANSPORT_BLUETOOTH,
|
||||||
|
ConnectivityManager.TYPE_BLUETOOTH);
|
||||||
|
sTransports.put(NetworkCapabilities.TRANSPORT_WIFI, ConnectivityManager.TYPE_WIFI);
|
||||||
|
sTransports.put(NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||||
|
ConnectivityManager.TYPE_MOBILE);
|
||||||
|
sTransports.put(NetworkCapabilities.TRANSPORT_LOWPAN, ConnectivityManager.TYPE_NONE);
|
||||||
|
sTransports.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
|
||||||
|
ConnectivityManager.TYPE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
long refCount = 0;
|
||||||
|
|
||||||
|
private class EthernetIpClientCallback extends IpClientCallbacks {
|
||||||
|
private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
|
||||||
|
private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
|
||||||
|
@Nullable INetworkInterfaceOutcomeReceiver mNetworkManagementListener;
|
||||||
|
|
||||||
|
EthernetIpClientCallback(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
mNetworkManagementListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onIpClientCreated(IIpClient ipClient) {
|
||||||
|
mIpClient = mDeps.makeIpClientManager(ipClient);
|
||||||
|
mIpClientStartCv.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void awaitIpClientStart() {
|
||||||
|
mIpClientStartCv.block();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void awaitIpClientShutdown() {
|
||||||
|
mIpClientShutdownCv.block();
|
||||||
|
}
|
||||||
|
|
||||||
|
// At the time IpClient is stopped, an IpClient event may have already been posted on
|
||||||
|
// the back of the handler and is awaiting execution. Once that event is executed, the
|
||||||
|
// associated callback object may not be valid anymore
|
||||||
|
// (NetworkInterfaceState#mIpClientCallback points to a different object / null).
|
||||||
|
private boolean isCurrentCallback() {
|
||||||
|
return this == mIpClientCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIpEvent(final @NonNull Runnable r) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
if (!isCurrentCallback()) {
|
||||||
|
Log.i(TAG, "Ignoring stale IpClientCallbacks " + this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvisioningSuccess(LinkProperties newLp) {
|
||||||
|
handleIpEvent(() -> onIpLayerStarted(newLp, mNetworkManagementListener));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvisioningFailure(LinkProperties newLp) {
|
||||||
|
// This cannot happen due to provisioning timeout, because our timeout is 0. It can
|
||||||
|
// happen due to errors while provisioning or on provisioning loss.
|
||||||
|
handleIpEvent(() -> onIpLayerStopped(mNetworkManagementListener));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLinkPropertiesChange(LinkProperties newLp) {
|
||||||
|
handleIpEvent(() -> updateLinkProperties(newLp));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReachabilityLost(String logMsg) {
|
||||||
|
handleIpEvent(() -> updateNeighborLostEvent(logMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQuit() {
|
||||||
|
mIpClient = null;
|
||||||
|
mIpClientShutdownCv.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
|
||||||
|
@NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
|
||||||
|
NetworkFactory networkFactory, Dependencies deps) {
|
||||||
|
name = ifaceName;
|
||||||
|
mIpConfig = Objects.requireNonNull(ipConfig);
|
||||||
|
mCapabilities = Objects.requireNonNull(capabilities);
|
||||||
|
mLegacyType = getLegacyType(mCapabilities);
|
||||||
|
mHandler = handler;
|
||||||
|
mContext = context;
|
||||||
|
mNetworkFactory = networkFactory;
|
||||||
|
mDeps = deps;
|
||||||
|
mHwAddress = hwAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the legacy transport type from a NetworkCapabilities transport type. Defaults
|
||||||
|
* to legacy TYPE_NONE if there is no known conversion
|
||||||
|
*/
|
||||||
|
private static int getLegacyType(int transport) {
|
||||||
|
return sTransports.get(transport, ConnectivityManager.TYPE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLegacyType(@NonNull final NetworkCapabilities capabilities) {
|
||||||
|
final int[] transportTypes = capabilities.getTransportTypes();
|
||||||
|
if (transportTypes.length > 0) {
|
||||||
|
return getLegacyType(transportTypes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never happen as transport is always one of ETHERNET or a valid override
|
||||||
|
throw new ConfigurationException("Network Capabilities do not have an associated "
|
||||||
|
+ "transport type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
|
||||||
|
mCapabilities = new NetworkCapabilities(capabilities);
|
||||||
|
mLegacyType = getLegacyType(mCapabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateInterface(@Nullable final IpConfiguration ipConfig,
|
||||||
|
@Nullable final NetworkCapabilities capabilities,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, "updateInterface, iface: " + name
|
||||||
|
+ ", ipConfig: " + ipConfig + ", old ipConfig: " + mIpConfig
|
||||||
|
+ ", capabilities: " + capabilities + ", old capabilities: " + mCapabilities
|
||||||
|
+ ", listener: " + listener
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != ipConfig){
|
||||||
|
mIpConfig = ipConfig;
|
||||||
|
}
|
||||||
|
if (null != capabilities) {
|
||||||
|
setCapabilities(capabilities);
|
||||||
|
}
|
||||||
|
// Send an abort callback if a request is filed before the previous one has completed.
|
||||||
|
maybeSendNetworkManagementCallbackForAbort();
|
||||||
|
// TODO: Update this logic to only do a restart if required. Although a restart may
|
||||||
|
// be required due to the capabilities or ipConfiguration values, not all
|
||||||
|
// capabilities changes require a restart.
|
||||||
|
restart(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isRestricted() {
|
||||||
|
return !mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start() {
|
||||||
|
start(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (mIpClient != null) {
|
||||||
|
if (DBG) Log.d(TAG, "IpClient already started");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DBG) {
|
||||||
|
Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
mIpClientCallback = new EthernetIpClientCallback(listener);
|
||||||
|
mDeps.makeIpClient(mContext, name, mIpClientCallback);
|
||||||
|
mIpClientCallback.awaitIpClientStart();
|
||||||
|
|
||||||
|
if (sTcpBufferSizes == null) {
|
||||||
|
sTcpBufferSizes = mDeps.getTcpBufferSizesFromResource(mContext);
|
||||||
|
}
|
||||||
|
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIpLayerStarted(@NonNull final LinkProperties linkProperties,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (mNetworkAgent != null) {
|
||||||
|
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mLinkProperties = linkProperties;
|
||||||
|
|
||||||
|
// Create our NetworkAgent.
|
||||||
|
final NetworkAgentConfig config = new NetworkAgentConfig.Builder()
|
||||||
|
.setLegacyType(mLegacyType)
|
||||||
|
.setLegacyTypeName(NETWORK_TYPE)
|
||||||
|
.setLegacyExtraInfo(mHwAddress)
|
||||||
|
.build();
|
||||||
|
mNetworkAgent = mDeps.makeEthernetNetworkAgent(mContext, mHandler.getLooper(),
|
||||||
|
mCapabilities, mLinkProperties, config, mNetworkFactory.getProvider(),
|
||||||
|
new EthernetNetworkAgent.Callbacks() {
|
||||||
|
@Override
|
||||||
|
public void onNetworkUnwanted() {
|
||||||
|
// if mNetworkAgent is null, we have already called stop.
|
||||||
|
if (mNetworkAgent == null) return;
|
||||||
|
|
||||||
|
if (this == mNetworkAgent.getCallbacks()) {
|
||||||
|
stop();
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Ignoring unwanted as we have a more modern " +
|
||||||
|
"instance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mNetworkAgent.register();
|
||||||
|
mNetworkAgent.markConnected();
|
||||||
|
realizeNetworkManagementCallback(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIpLayerStopped(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
// There is no point in continuing if the interface is gone as stop() will be triggered
|
||||||
|
// by removeInterface() when processed on the handler thread and start() won't
|
||||||
|
// work for a non-existent interface.
|
||||||
|
if (null == mDeps.getNetworkInterfaceByName(name)) {
|
||||||
|
if (DBG) Log.d(TAG, name + " is no longer available.");
|
||||||
|
// Send a callback in case a provisioning request was in progress.
|
||||||
|
maybeSendNetworkManagementCallbackForAbort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
restart(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeSendNetworkManagementCallbackForAbort() {
|
||||||
|
realizeNetworkManagementCallback(null,
|
||||||
|
new EthernetNetworkManagementException(
|
||||||
|
"The IP provisioning request has been aborted."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called on the handler thread
|
||||||
|
private void realizeNetworkManagementCallback(@Nullable final String iface,
|
||||||
|
@Nullable final EthernetNetworkManagementException e) {
|
||||||
|
ensureRunningOnEthernetHandlerThread();
|
||||||
|
if (null == mIpClientCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EthernetNetworkFactory.maybeSendNetworkManagementCallback(
|
||||||
|
mIpClientCallback.mNetworkManagementListener, iface, e);
|
||||||
|
// Only send a single callback per listener.
|
||||||
|
mIpClientCallback.mNetworkManagementListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureRunningOnEthernetHandlerThread() {
|
||||||
|
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Not running on the Ethernet thread: "
|
||||||
|
+ Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateLinkProperties(LinkProperties linkProperties) {
|
||||||
|
mLinkProperties = linkProperties;
|
||||||
|
if (mNetworkAgent != null) {
|
||||||
|
mNetworkAgent.sendLinkPropertiesImpl(linkProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateNeighborLostEvent(String logMsg) {
|
||||||
|
Log.i(TAG, "updateNeighborLostEvent " + logMsg);
|
||||||
|
// Reachability lost will be seen only if the gateway is not reachable.
|
||||||
|
// Since ethernet FW doesn't have the mechanism to scan for new networks
|
||||||
|
// like WiFi, simply restart.
|
||||||
|
// If there is a better network, that will become default and apps
|
||||||
|
// will be able to use internet. If ethernet gets connected again,
|
||||||
|
// and has backhaul connectivity, it will become default.
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if state has been modified */
|
||||||
|
boolean updateLinkState(final boolean up,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (mLinkUp == up) {
|
||||||
|
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, null,
|
||||||
|
new EthernetNetworkManagementException(
|
||||||
|
"No changes with requested link state " + up + " for " + name));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mLinkUp = up;
|
||||||
|
|
||||||
|
if (!up) { // was up, goes down
|
||||||
|
// Send an abort on a provisioning request callback if necessary before stopping.
|
||||||
|
maybeSendNetworkManagementCallbackForAbort();
|
||||||
|
stop();
|
||||||
|
// If only setting the interface down, send a callback to signal completion.
|
||||||
|
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
|
||||||
|
} else { // was down, goes up
|
||||||
|
stop();
|
||||||
|
start(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
// Invalidate all previous start requests
|
||||||
|
if (mIpClient != null) {
|
||||||
|
mIpClient.shutdown();
|
||||||
|
mIpClientCallback.awaitIpClientShutdown();
|
||||||
|
mIpClient = null;
|
||||||
|
}
|
||||||
|
mIpClientCallback = null;
|
||||||
|
|
||||||
|
if (mNetworkAgent != null) {
|
||||||
|
mNetworkAgent.unregister();
|
||||||
|
mNetworkAgent = null;
|
||||||
|
}
|
||||||
|
mLinkProperties.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void provisionIpClient(@NonNull final IpClientManager ipClient,
|
||||||
|
@NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
|
||||||
|
if (config.getProxySettings() == ProxySettings.STATIC ||
|
||||||
|
config.getProxySettings() == ProxySettings.PAC) {
|
||||||
|
ipClient.setHttpProxy(config.getHttpProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(tcpBufferSizes)) {
|
||||||
|
ipClient.setTcpBufferSizes(tcpBufferSizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipClient.startProvisioning(createProvisioningConfiguration(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProvisioningConfiguration createProvisioningConfiguration(
|
||||||
|
@NonNull final IpConfiguration config) {
|
||||||
|
if (config.getIpAssignment() == IpAssignment.STATIC) {
|
||||||
|
return new ProvisioningConfiguration.Builder()
|
||||||
|
.withStaticConfiguration(config.getStaticIpConfiguration())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return new ProvisioningConfiguration.Builder()
|
||||||
|
.withProvisioningTimeoutMs(0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void restart() {
|
||||||
|
restart(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void restart(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (DBG) Log.d(TAG, "reconnecting Ethernet");
|
||||||
|
stop();
|
||||||
|
start(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "{ "
|
||||||
|
+ "refCount: " + refCount + ", "
|
||||||
|
+ "iface: " + name + ", "
|
||||||
|
+ "up: " + mLinkUp + ", "
|
||||||
|
+ "hwAddress: " + mHwAddress + ", "
|
||||||
|
+ "networkCapabilities: " + mCapabilities + ", "
|
||||||
|
+ "networkAgent: " + mNetworkAgent + ", "
|
||||||
|
+ "ipClient: " + mIpClient + ","
|
||||||
|
+ "linkProperties: " + mLinkProperties
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
|
||||||
|
super.dump(fd, pw, args);
|
||||||
|
pw.println(getClass().getSimpleName());
|
||||||
|
pw.println("Tracking interfaces:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (String iface: mTrackingInterfaces.keySet()) {
|
||||||
|
NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
|
||||||
|
pw.println(iface + ":" + ifaceState);
|
||||||
|
pw.increaseIndent();
|
||||||
|
if (null == ifaceState.mIpClient) {
|
||||||
|
pw.println("IpClient is null");
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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 android.content.Context;
|
||||||
|
import android.net.INetd;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
// TODO: consider renaming EthernetServiceImpl to EthernetService and deleting this file.
|
||||||
|
public final class EthernetService {
|
||||||
|
private static final String TAG = "EthernetService";
|
||||||
|
private static final String THREAD_NAME = "EthernetServiceThread";
|
||||||
|
|
||||||
|
private static INetd getNetd(Context context) {
|
||||||
|
final INetd netd =
|
||||||
|
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
|
||||||
|
Objects.requireNonNull(netd, "could not get netd instance");
|
||||||
|
return netd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EthernetServiceImpl create(Context context) {
|
||||||
|
final HandlerThread handlerThread = new HandlerThread(THREAD_NAME);
|
||||||
|
handlerThread.start();
|
||||||
|
final Handler handler = new Handler(handlerThread.getLooper());
|
||||||
|
final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
|
||||||
|
return new EthernetServiceImpl(context, handler,
|
||||||
|
new EthernetTracker(context, handler, factory, getNetd(context)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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 android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.IEthernetManager;
|
||||||
|
import android.net.IEthernetServiceListener;
|
||||||
|
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||||
|
import android.net.ITetheredInterfaceCallback;
|
||||||
|
import android.net.EthernetNetworkUpdateRequest;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.PrintWriterPrinter;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
import com.android.net.module.util.PermissionUtils;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EthernetServiceImpl handles remote Ethernet operation requests by implementing
|
||||||
|
* the IEthernetManager interface.
|
||||||
|
*/
|
||||||
|
public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||||
|
private static final String TAG = "EthernetServiceImpl";
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
final AtomicBoolean mStarted = new AtomicBoolean(false);
|
||||||
|
private final Context mContext;
|
||||||
|
private final Handler mHandler;
|
||||||
|
private final EthernetTracker mTracker;
|
||||||
|
|
||||||
|
EthernetServiceImpl(@NonNull final Context context, @NonNull final Handler handler,
|
||||||
|
@NonNull final EthernetTracker tracker) {
|
||||||
|
mContext = context;
|
||||||
|
mHandler = handler;
|
||||||
|
mTracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceAutomotiveDevice(final @NonNull String methodName) {
|
||||||
|
PermissionUtils.enforceSystemFeature(mContext, PackageManager.FEATURE_AUTOMOTIVE,
|
||||||
|
methodName + " is only available on automotive devices.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkUseRestrictedNetworksPermission() {
|
||||||
|
return PermissionUtils.checkAnyPermissionOf(mContext,
|
||||||
|
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
Log.i(TAG, "Starting Ethernet service");
|
||||||
|
mTracker.start();
|
||||||
|
mStarted.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwIfEthernetNotStarted() {
|
||||||
|
if (!mStarted.get()) {
|
||||||
|
throw new IllegalStateException("System isn't ready to change ethernet configurations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getAvailableInterfaces() throws RemoteException {
|
||||||
|
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||||
|
return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Ethernet configuration
|
||||||
|
* @return the Ethernet Configuration, contained in {@link IpConfiguration}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IpConfiguration getConfiguration(String iface) {
|
||||||
|
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||||
|
if (mTracker.isRestrictedInterface(iface)) {
|
||||||
|
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IpConfiguration(mTracker.getIpConfiguration(iface));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Ethernet configuration
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setConfiguration(String iface, IpConfiguration config) {
|
||||||
|
throwIfEthernetNotStarted();
|
||||||
|
|
||||||
|
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||||
|
if (mTracker.isRestrictedInterface(iface)) {
|
||||||
|
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this does not check proxy settings, gateways, etc.
|
||||||
|
// Fix this by making IpConfiguration a complete representation of static configuration.
|
||||||
|
mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether given interface is available.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable(String iface) {
|
||||||
|
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||||
|
if (mTracker.isRestrictedInterface(iface)) {
|
||||||
|
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTracker.isTrackingInterface(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener.
|
||||||
|
* @param listener A {@link IEthernetServiceListener} to add.
|
||||||
|
*/
|
||||||
|
public void addListener(IEthernetServiceListener listener) throws RemoteException {
|
||||||
|
Objects.requireNonNull(listener, "listener must not be null");
|
||||||
|
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||||
|
mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener.
|
||||||
|
* @param listener A {@link IEthernetServiceListener} to remove.
|
||||||
|
*/
|
||||||
|
public void removeListener(IEthernetServiceListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
throw new IllegalArgumentException("listener must not be null");
|
||||||
|
}
|
||||||
|
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||||
|
mTracker.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIncludeTestInterfaces(boolean include) {
|
||||||
|
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||||
|
android.Manifest.permission.NETWORK_SETTINGS);
|
||||||
|
mTracker.setIncludeTestInterfaces(include);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||||
|
Objects.requireNonNull(callback, "callback must not be null");
|
||||||
|
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||||
|
android.Manifest.permission.NETWORK_SETTINGS);
|
||||||
|
mTracker.requestTetheredInterface(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||||
|
Objects.requireNonNull(callback, "callback must not be null");
|
||||||
|
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||||
|
android.Manifest.permission.NETWORK_SETTINGS);
|
||||||
|
mTracker.releaseTetheredInterface(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||||
|
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
||||||
|
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
pw.println("Permission Denial: can't dump EthernetService from pid="
|
||||||
|
+ Binder.getCallingPid()
|
||||||
|
+ ", uid=" + Binder.getCallingUid());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.println("Current Ethernet state: ");
|
||||||
|
pw.increaseIndent();
|
||||||
|
mTracker.dump(fd, pw, args);
|
||||||
|
pw.decreaseIndent();
|
||||||
|
|
||||||
|
pw.println("Handler:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
mHandler.dump(new PrintWriterPrinter(pw), "EthernetServiceImpl");
|
||||||
|
pw.decreaseIndent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceNetworkManagementPermission() {
|
||||||
|
mContext.enforceCallingOrSelfPermission(
|
||||||
|
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS,
|
||||||
|
"EthernetServiceImpl");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceManageTestNetworksPermission() {
|
||||||
|
mContext.enforceCallingOrSelfPermission(
|
||||||
|
android.Manifest.permission.MANAGE_TEST_NETWORKS,
|
||||||
|
"EthernetServiceImpl");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeValidateTestCapabilities(final String iface,
|
||||||
|
@Nullable final NetworkCapabilities nc) {
|
||||||
|
if (!mTracker.isValidTestInterface(iface)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// For test interfaces, only null or capabilities that include TRANSPORT_TEST are
|
||||||
|
// allowed.
|
||||||
|
if (nc != null && !nc.hasTransport(TRANSPORT_TEST)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Updates to test interfaces must have NetworkCapabilities.TRANSPORT_TEST.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceAdminPermission(final String iface, boolean enforceAutomotive,
|
||||||
|
final String logMessage) {
|
||||||
|
if (mTracker.isValidTestInterface(iface)) {
|
||||||
|
enforceManageTestNetworksPermission();
|
||||||
|
} else {
|
||||||
|
enforceNetworkManagementPermission();
|
||||||
|
if (enforceAutomotive) {
|
||||||
|
enforceAutomotiveDevice(logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateConfiguration(@NonNull final String iface,
|
||||||
|
@NonNull final EthernetNetworkUpdateRequest request,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
Objects.requireNonNull(iface);
|
||||||
|
Objects.requireNonNull(request);
|
||||||
|
throwIfEthernetNotStarted();
|
||||||
|
|
||||||
|
// TODO: validate that iface is listed in overlay config_ethernet_interfaces
|
||||||
|
// only automotive devices are allowed to set the NetworkCapabilities using this API
|
||||||
|
enforceAdminPermission(iface, request.getNetworkCapabilities() != null,
|
||||||
|
"updateConfiguration() with non-null capabilities");
|
||||||
|
maybeValidateTestCapabilities(iface, request.getNetworkCapabilities());
|
||||||
|
|
||||||
|
mTracker.updateConfiguration(
|
||||||
|
iface, request.getIpConfiguration(), request.getNetworkCapabilities(), listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectNetwork(@NonNull final String iface,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
Log.i(TAG, "connectNetwork called with: iface=" + iface + ", listener=" + listener);
|
||||||
|
Objects.requireNonNull(iface);
|
||||||
|
throwIfEthernetNotStarted();
|
||||||
|
|
||||||
|
enforceAdminPermission(iface, true, "connectNetwork()");
|
||||||
|
|
||||||
|
mTracker.connectNetwork(iface, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnectNetwork(@NonNull final String iface,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
Log.i(TAG, "disconnectNetwork called with: iface=" + iface + ", listener=" + listener);
|
||||||
|
Objects.requireNonNull(iface);
|
||||||
|
throwIfEthernetNotStarted();
|
||||||
|
|
||||||
|
enforceAdminPermission(iface, true, "connectNetwork()");
|
||||||
|
|
||||||
|
mTracker.disconnectNetwork(iface, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEthernetEnabled(boolean enabled) {
|
||||||
|
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||||
|
android.Manifest.permission.NETWORK_SETTINGS);
|
||||||
|
|
||||||
|
mTracker.setEthernetEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getInterfaceList() {
|
||||||
|
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||||
|
return mTracker.getInterfaceList();
|
||||||
|
}
|
||||||
|
}
|
||||||
942
service-t/src/com/android/server/ethernet/EthernetTracker.java
Normal file
942
service-t/src/com/android/server/ethernet/EthernetTracker.java
Normal file
@@ -0,0 +1,942 @@
|
|||||||
|
/*
|
||||||
|
* 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.ethernet;
|
||||||
|
|
||||||
|
import static android.net.EthernetManager.ETHERNET_STATE_DISABLED;
|
||||||
|
import static android.net.EthernetManager.ETHERNET_STATE_ENABLED;
|
||||||
|
import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
|
||||||
|
|
||||||
|
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.net.ConnectivityResources;
|
||||||
|
import android.net.EthernetManager;
|
||||||
|
import android.net.IEthernetServiceListener;
|
||||||
|
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||||
|
import android.net.INetd;
|
||||||
|
import android.net.ITetheredInterfaceCallback;
|
||||||
|
import android.net.InterfaceConfigurationParcel;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.net.IpConfiguration.IpAssignment;
|
||||||
|
import android.net.IpConfiguration.ProxySettings;
|
||||||
|
import android.net.LinkAddress;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.net.StaticIpConfiguration;
|
||||||
|
import android.os.ConditionVariable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.RemoteCallbackList;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.ServiceSpecificException;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
|
||||||
|
import com.android.net.module.util.NetdUtils;
|
||||||
|
import com.android.net.module.util.PermissionUtils;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks Ethernet interfaces and manages interface configurations.
|
||||||
|
*
|
||||||
|
* <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
|
||||||
|
* in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
|
||||||
|
* not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
|
||||||
|
* Interfaces could have associated {@link android.net.IpConfiguration}.
|
||||||
|
* Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
|
||||||
|
* connected over USB). This class supports multiple interfaces. When an interface appears on the
|
||||||
|
* system (or is present at boot time) this class will start tracking it and bring it up. Only
|
||||||
|
* interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
|
||||||
|
* tracked.
|
||||||
|
*
|
||||||
|
* <p>All public or package private methods must be thread-safe unless stated otherwise.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(visibility = PACKAGE)
|
||||||
|
public class EthernetTracker {
|
||||||
|
private static final int INTERFACE_MODE_CLIENT = 1;
|
||||||
|
private static final int INTERFACE_MODE_SERVER = 2;
|
||||||
|
|
||||||
|
private static final String TAG = EthernetTracker.class.getSimpleName();
|
||||||
|
private static final boolean DBG = EthernetNetworkFactory.DBG;
|
||||||
|
|
||||||
|
private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+";
|
||||||
|
private static final String LEGACY_IFACE_REGEXP = "eth\\d";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface names we track. This is a product-dependent regular expression, plus,
|
||||||
|
* if setIncludeTestInterfaces is true, any test interfaces.
|
||||||
|
*/
|
||||||
|
private volatile String mIfaceMatch;
|
||||||
|
/**
|
||||||
|
* Track test interfaces if true, don't track otherwise.
|
||||||
|
*/
|
||||||
|
private boolean mIncludeTestInterfaces = false;
|
||||||
|
|
||||||
|
/** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
|
||||||
|
private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final INetd mNetd;
|
||||||
|
private final Handler mHandler;
|
||||||
|
private final EthernetNetworkFactory mFactory;
|
||||||
|
private final EthernetConfigStore mConfigStore;
|
||||||
|
private final Dependencies mDeps;
|
||||||
|
|
||||||
|
private final RemoteCallbackList<IEthernetServiceListener> mListeners =
|
||||||
|
new RemoteCallbackList<>();
|
||||||
|
private final TetheredInterfaceRequestList mTetheredInterfaceRequests =
|
||||||
|
new TetheredInterfaceRequestList();
|
||||||
|
|
||||||
|
// Used only on the handler thread
|
||||||
|
private String mDefaultInterface;
|
||||||
|
private int mDefaultInterfaceMode = INTERFACE_MODE_CLIENT;
|
||||||
|
// Tracks whether clients were notified that the tethered interface is available
|
||||||
|
private boolean mTetheredInterfaceWasAvailable = false;
|
||||||
|
private volatile IpConfiguration mIpConfigForDefaultInterface;
|
||||||
|
|
||||||
|
private int mEthernetState = ETHERNET_STATE_ENABLED;
|
||||||
|
|
||||||
|
private class TetheredInterfaceRequestList extends
|
||||||
|
RemoteCallbackList<ITetheredInterfaceCallback> {
|
||||||
|
@Override
|
||||||
|
public void onCallbackDied(ITetheredInterfaceCallback cb, Object cookie) {
|
||||||
|
mHandler.post(EthernetTracker.this::maybeUntetherDefaultInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Dependencies {
|
||||||
|
// TODO: remove legacy resource fallback after migrating its overlays.
|
||||||
|
private String getPlatformRegexResource(Context context) {
|
||||||
|
final Resources r = context.getResources();
|
||||||
|
final int resId =
|
||||||
|
r.getIdentifier("config_ethernet_iface_regex", "string", context.getPackageName());
|
||||||
|
return r.getString(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove legacy resource fallback after migrating its overlays.
|
||||||
|
private String[] getPlatformInterfaceConfigs(Context context) {
|
||||||
|
final Resources r = context.getResources();
|
||||||
|
final int resId = r.getIdentifier("config_ethernet_interfaces", "array",
|
||||||
|
context.getPackageName());
|
||||||
|
return r.getStringArray(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInterfaceRegexFromResource(Context context) {
|
||||||
|
final String platformRegex = getPlatformRegexResource(context);
|
||||||
|
final String match;
|
||||||
|
if (!LEGACY_IFACE_REGEXP.equals(platformRegex)) {
|
||||||
|
// Platform resource is not the historical default: use the overlay
|
||||||
|
match = platformRegex;
|
||||||
|
} else {
|
||||||
|
final ConnectivityResources resources = new ConnectivityResources(context);
|
||||||
|
match = resources.get().getString(
|
||||||
|
com.android.connectivity.resources.R.string.config_ethernet_iface_regex);
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getInterfaceConfigFromResource(Context context) {
|
||||||
|
final String[] platformInterfaceConfigs = getPlatformInterfaceConfigs(context);
|
||||||
|
final String[] interfaceConfigs;
|
||||||
|
if (platformInterfaceConfigs.length != 0) {
|
||||||
|
// Platform resource is not the historical default: use the overlay
|
||||||
|
interfaceConfigs = platformInterfaceConfigs;
|
||||||
|
} else {
|
||||||
|
final ConnectivityResources resources = new ConnectivityResources(context);
|
||||||
|
interfaceConfigs = resources.get().getStringArray(
|
||||||
|
com.android.connectivity.resources.R.array.config_ethernet_interfaces);
|
||||||
|
}
|
||||||
|
return interfaceConfigs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
|
||||||
|
@NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) {
|
||||||
|
this(context, handler, factory, netd, new Dependencies());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
|
||||||
|
@NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd,
|
||||||
|
@NonNull final Dependencies deps) {
|
||||||
|
mContext = context;
|
||||||
|
mHandler = handler;
|
||||||
|
mFactory = factory;
|
||||||
|
mNetd = netd;
|
||||||
|
mDeps = deps;
|
||||||
|
|
||||||
|
// Interface match regex.
|
||||||
|
updateIfaceMatchRegexp();
|
||||||
|
|
||||||
|
// Read default Ethernet interface configuration from resources
|
||||||
|
final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context);
|
||||||
|
for (String strConfig : interfaceConfigs) {
|
||||||
|
parseEthernetConfig(strConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
mConfigStore = new EthernetConfigStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
mFactory.register();
|
||||||
|
mConfigStore.read();
|
||||||
|
|
||||||
|
// Default interface is just the first one we want to track.
|
||||||
|
mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
|
||||||
|
final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
|
||||||
|
for (int i = 0; i < configs.size(); i++) {
|
||||||
|
mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||||
|
mNetd.registerUnsolicitedEventListener(new InterfaceObserver());
|
||||||
|
} catch (RemoteException | ServiceSpecificException e) {
|
||||||
|
Log.e(TAG, "Could not register InterfaceObserver " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandler.post(this::trackAvailableInterfaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
|
||||||
|
}
|
||||||
|
writeIpConfiguration(iface, ipConfiguration);
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mFactory.updateInterface(iface, ipConfiguration, null, null);
|
||||||
|
broadcastInterfaceStateChange(iface);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeIpConfiguration(@NonNull final String iface,
|
||||||
|
@NonNull final IpConfiguration ipConfig) {
|
||||||
|
mConfigStore.write(iface, ipConfig);
|
||||||
|
mIpConfigurations.put(iface, ipConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IpConfiguration getIpConfigurationForCallback(String iface, int state) {
|
||||||
|
return (state == EthernetManager.STATE_ABSENT) ? null : getOrCreateIpConfiguration(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureRunningOnEthernetServiceThread() {
|
||||||
|
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Not running on EthernetService thread: "
|
||||||
|
+ Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast the link state or IpConfiguration change of existing Ethernet interfaces to all
|
||||||
|
* listeners.
|
||||||
|
*/
|
||||||
|
protected void broadcastInterfaceStateChange(@NonNull String iface) {
|
||||||
|
ensureRunningOnEthernetServiceThread();
|
||||||
|
final int state = mFactory.getInterfaceState(iface);
|
||||||
|
final int role = getInterfaceRole(iface);
|
||||||
|
final IpConfiguration config = getIpConfigurationForCallback(iface, state);
|
||||||
|
final int n = mListeners.beginBroadcast();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
try {
|
||||||
|
mListeners.getBroadcastItem(i).onInterfaceStateChanged(iface, state, role, config);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mListeners.finishBroadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unicast the interface state or IpConfiguration change of existing Ethernet interfaces to a
|
||||||
|
* specific listener.
|
||||||
|
*/
|
||||||
|
protected void unicastInterfaceStateChange(@NonNull IEthernetServiceListener listener,
|
||||||
|
@NonNull String iface) {
|
||||||
|
ensureRunningOnEthernetServiceThread();
|
||||||
|
final int state = mFactory.getInterfaceState(iface);
|
||||||
|
final int role = getInterfaceRole(iface);
|
||||||
|
final IpConfiguration config = getIpConfigurationForCallback(iface, state);
|
||||||
|
try {
|
||||||
|
listener.onInterfaceStateChanged(iface, state, role, config);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = PACKAGE)
|
||||||
|
protected void updateConfiguration(@NonNull final String iface,
|
||||||
|
@Nullable final IpConfiguration ipConfig,
|
||||||
|
@Nullable final NetworkCapabilities capabilities,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
|
||||||
|
+ ", ipConfig: " + ipConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
final IpConfiguration localIpConfig = ipConfig == null
|
||||||
|
? null : new IpConfiguration(ipConfig);
|
||||||
|
if (ipConfig != null) {
|
||||||
|
writeIpConfiguration(iface, localIpConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != capabilities) {
|
||||||
|
mNetworkCapabilities.put(iface, capabilities);
|
||||||
|
}
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mFactory.updateInterface(iface, localIpConfig, capabilities, listener);
|
||||||
|
broadcastInterfaceStateChange(iface);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = PACKAGE)
|
||||||
|
protected void connectNetwork(@NonNull final String iface,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
mHandler.post(() -> updateInterfaceState(iface, true, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = PACKAGE)
|
||||||
|
protected void disconnectNetwork(@NonNull final String iface,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
mHandler.post(() -> updateInterfaceState(iface, false, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
IpConfiguration getIpConfiguration(String iface) {
|
||||||
|
return mIpConfigurations.get(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = PACKAGE)
|
||||||
|
protected boolean isTrackingInterface(String iface) {
|
||||||
|
return mFactory.hasInterface(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] getInterfaces(boolean includeRestricted) {
|
||||||
|
return mFactory.getAvailableInterfaces(includeRestricted);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getInterfaceList() {
|
||||||
|
final List<String> interfaceList = new ArrayList<String>();
|
||||||
|
final String[] ifaces;
|
||||||
|
try {
|
||||||
|
ifaces = mNetd.interfaceGetList();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Could not get list of interfaces " + e);
|
||||||
|
return interfaceList;
|
||||||
|
}
|
||||||
|
final String ifaceMatch = mIfaceMatch;
|
||||||
|
for (String iface : ifaces) {
|
||||||
|
if (iface.matches(ifaceMatch)) interfaceList.add(iface);
|
||||||
|
}
|
||||||
|
return interfaceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if given interface was configured as restricted (doesn't have
|
||||||
|
* NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
|
||||||
|
*/
|
||||||
|
boolean isRestrictedInterface(String iface) {
|
||||||
|
final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
|
||||||
|
return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
if (!mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks))) {
|
||||||
|
// Remote process has already died
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String iface : getInterfaces(canUseRestrictedNetworks)) {
|
||||||
|
unicastInterfaceStateChange(listener, iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
unicastEthernetStateChange(listener, mEthernetState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeListener(IEthernetServiceListener listener) {
|
||||||
|
mHandler.post(() -> mListeners.unregister(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIncludeTestInterfaces(boolean include) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mIncludeTestInterfaces = include;
|
||||||
|
updateIfaceMatchRegexp();
|
||||||
|
mHandler.post(() -> trackAvailableInterfaces());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
if (!mTetheredInterfaceRequests.register(callback)) {
|
||||||
|
// Remote process has already died
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mDefaultInterfaceMode == INTERFACE_MODE_SERVER) {
|
||||||
|
if (mTetheredInterfaceWasAvailable) {
|
||||||
|
notifyTetheredInterfaceAvailable(callback, mDefaultInterface);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultInterfaceMode(INTERFACE_MODE_SERVER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
mTetheredInterfaceRequests.unregister(callback);
|
||||||
|
maybeUntetherDefaultInterface();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyTetheredInterfaceAvailable(ITetheredInterfaceCallback cb, String iface) {
|
||||||
|
try {
|
||||||
|
cb.onAvailable(iface);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Error sending tethered interface available callback", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyTetheredInterfaceUnavailable(ITetheredInterfaceCallback cb) {
|
||||||
|
try {
|
||||||
|
cb.onUnavailable();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Error sending tethered interface available callback", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUntetherDefaultInterface() {
|
||||||
|
if (mTetheredInterfaceRequests.getRegisteredCallbackCount() > 0) return;
|
||||||
|
if (mDefaultInterfaceMode == INTERFACE_MODE_CLIENT) return;
|
||||||
|
setDefaultInterfaceMode(INTERFACE_MODE_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDefaultInterfaceMode(int mode) {
|
||||||
|
Log.d(TAG, "Setting default interface mode to " + mode);
|
||||||
|
mDefaultInterfaceMode = mode;
|
||||||
|
if (mDefaultInterface != null) {
|
||||||
|
removeInterface(mDefaultInterface);
|
||||||
|
addInterface(mDefaultInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInterfaceRole(final String iface) {
|
||||||
|
if (!mFactory.hasInterface(iface)) return EthernetManager.ROLE_NONE;
|
||||||
|
final int mode = getInterfaceMode(iface);
|
||||||
|
return (mode == INTERFACE_MODE_CLIENT)
|
||||||
|
? EthernetManager.ROLE_CLIENT
|
||||||
|
: EthernetManager.ROLE_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInterfaceMode(final String iface) {
|
||||||
|
if (iface.equals(mDefaultInterface)) {
|
||||||
|
return mDefaultInterfaceMode;
|
||||||
|
}
|
||||||
|
return INTERFACE_MODE_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeInterface(String iface) {
|
||||||
|
mFactory.removeInterface(iface);
|
||||||
|
maybeUpdateServerModeInterfaceState(iface, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopTrackingInterface(String iface) {
|
||||||
|
removeInterface(iface);
|
||||||
|
if (iface.equals(mDefaultInterface)) {
|
||||||
|
mDefaultInterface = null;
|
||||||
|
}
|
||||||
|
broadcastInterfaceStateChange(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInterface(String iface) {
|
||||||
|
InterfaceConfigurationParcel config = null;
|
||||||
|
// Bring up the interface so we get link status indications.
|
||||||
|
try {
|
||||||
|
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||||
|
NetdUtils.setInterfaceUp(mNetd, iface);
|
||||||
|
config = NetdUtils.getInterfaceConfigParcel(mNetd, iface);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// Either the system is crashing or the interface has disappeared. Just ignore the
|
||||||
|
// error; we haven't modified any state because we only do that if our calls succeed.
|
||||||
|
Log.e(TAG, "Error upping interface " + iface, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
Log.e(TAG, "Null interface config parcelable for " + iface + ". Bailing out.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String hwAddress = config.hwAddr;
|
||||||
|
|
||||||
|
NetworkCapabilities nc = mNetworkCapabilities.get(iface);
|
||||||
|
if (nc == null) {
|
||||||
|
// Try to resolve using mac address
|
||||||
|
nc = mNetworkCapabilities.get(hwAddress);
|
||||||
|
if (nc == null) {
|
||||||
|
final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP);
|
||||||
|
nc = createDefaultNetworkCapabilities(isTestIface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int mode = getInterfaceMode(iface);
|
||||||
|
if (mode == INTERFACE_MODE_CLIENT) {
|
||||||
|
IpConfiguration ipConfiguration = getOrCreateIpConfiguration(iface);
|
||||||
|
Log.d(TAG, "Tracking interface in client mode: " + iface);
|
||||||
|
mFactory.addInterface(iface, hwAddress, ipConfiguration, nc);
|
||||||
|
} else {
|
||||||
|
maybeUpdateServerModeInterfaceState(iface, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: if the interface already has link (e.g., if we crashed and got
|
||||||
|
// restarted while it was running), we need to fake a link up notification so we
|
||||||
|
// start configuring it.
|
||||||
|
if (NetdUtils.hasFlag(config, "running")) {
|
||||||
|
updateInterfaceState(iface, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInterfaceState(String iface, boolean up) {
|
||||||
|
updateInterfaceState(iface, up, null /* listener */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInterfaceState(@NonNull final String iface, final boolean up,
|
||||||
|
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||||
|
final int mode = getInterfaceMode(iface);
|
||||||
|
final boolean factoryLinkStateUpdated = (mode == INTERFACE_MODE_CLIENT)
|
||||||
|
&& mFactory.updateInterfaceLinkState(iface, up, listener);
|
||||||
|
|
||||||
|
if (factoryLinkStateUpdated) {
|
||||||
|
broadcastInterfaceStateChange(iface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateServerModeInterfaceState(String iface, boolean available) {
|
||||||
|
if (available == mTetheredInterfaceWasAvailable || !iface.equals(mDefaultInterface)) return;
|
||||||
|
|
||||||
|
Log.d(TAG, (available ? "Tracking" : "No longer tracking")
|
||||||
|
+ " interface in server mode: " + iface);
|
||||||
|
|
||||||
|
final int pendingCbs = mTetheredInterfaceRequests.beginBroadcast();
|
||||||
|
for (int i = 0; i < pendingCbs; i++) {
|
||||||
|
ITetheredInterfaceCallback item = mTetheredInterfaceRequests.getBroadcastItem(i);
|
||||||
|
if (available) {
|
||||||
|
notifyTetheredInterfaceAvailable(item, iface);
|
||||||
|
} else {
|
||||||
|
notifyTetheredInterfaceUnavailable(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTetheredInterfaceRequests.finishBroadcast();
|
||||||
|
mTetheredInterfaceWasAvailable = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeTrackInterface(String iface) {
|
||||||
|
if (!iface.matches(mIfaceMatch)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't already track this interface, and if this interface matches
|
||||||
|
// our regex, start tracking it.
|
||||||
|
if (mFactory.hasInterface(iface) || iface.equals(mDefaultInterface)) {
|
||||||
|
if (DBG) Log.w(TAG, "Ignoring already-tracked interface " + iface);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DBG) Log.i(TAG, "maybeTrackInterface: " + iface);
|
||||||
|
|
||||||
|
// TODO: avoid making an interface default if it has configured NetworkCapabilities.
|
||||||
|
if (mDefaultInterface == null) {
|
||||||
|
mDefaultInterface = iface;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIpConfigForDefaultInterface != null) {
|
||||||
|
updateIpConfiguration(iface, mIpConfigForDefaultInterface);
|
||||||
|
mIpConfigForDefaultInterface = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addInterface(iface);
|
||||||
|
|
||||||
|
broadcastInterfaceStateChange(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trackAvailableInterfaces() {
|
||||||
|
try {
|
||||||
|
final String[] ifaces = mNetd.interfaceGetList();
|
||||||
|
for (String iface : ifaces) {
|
||||||
|
maybeTrackInterface(iface);
|
||||||
|
}
|
||||||
|
} catch (RemoteException | ServiceSpecificException e) {
|
||||||
|
Log.e(TAG, "Could not get list of interfaces " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInterfaceLinkStateChanged(String iface, boolean up) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
|
||||||
|
}
|
||||||
|
mHandler.post(() -> updateInterfaceState(iface, up));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInterfaceAdded(String iface) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.i(TAG, "onInterfaceAdded, iface: " + iface);
|
||||||
|
}
|
||||||
|
mHandler.post(() -> maybeTrackInterface(iface));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInterfaceRemoved(String iface) {
|
||||||
|
if (DBG) {
|
||||||
|
Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
|
||||||
|
}
|
||||||
|
mHandler.post(() -> stopTrackingInterface(iface));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ListenerInfo {
|
||||||
|
|
||||||
|
boolean canUseRestrictedNetworks = false;
|
||||||
|
|
||||||
|
ListenerInfo(boolean canUseRestrictedNetworks) {
|
||||||
|
this.canUseRestrictedNetworks = canUseRestrictedNetworks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an Ethernet interface configuration
|
||||||
|
*
|
||||||
|
* @param configString represents an Ethernet configuration in the following format: {@code
|
||||||
|
* <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]}
|
||||||
|
*/
|
||||||
|
private void parseEthernetConfig(String configString) {
|
||||||
|
final EthernetTrackerConfig config = createEthernetTrackerConfig(configString);
|
||||||
|
NetworkCapabilities nc = createNetworkCapabilities(
|
||||||
|
!TextUtils.isEmpty(config.mCapabilities) /* clear default capabilities */,
|
||||||
|
config.mCapabilities, config.mTransport).build();
|
||||||
|
mNetworkCapabilities.put(config.mIface, nc);
|
||||||
|
|
||||||
|
if (null != config.mIpConfig) {
|
||||||
|
IpConfiguration ipConfig = parseStaticIpConfiguration(config.mIpConfig);
|
||||||
|
mIpConfigurations.put(config.mIface, ipConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static EthernetTrackerConfig createEthernetTrackerConfig(@NonNull final String configString) {
|
||||||
|
Objects.requireNonNull(configString, "EthernetTrackerConfig requires non-null config");
|
||||||
|
return new EthernetTrackerConfig(configString.split(";", /* limit of tokens */ 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) {
|
||||||
|
NetworkCapabilities.Builder builder = createNetworkCapabilities(
|
||||||
|
false /* clear default capabilities */, null, null)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||||
|
|
||||||
|
if (isTestIface) {
|
||||||
|
builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
|
||||||
|
} else {
|
||||||
|
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a static list of network capabilities
|
||||||
|
*
|
||||||
|
* @param clearDefaultCapabilities Indicates whether or not to clear any default capabilities
|
||||||
|
* @param commaSeparatedCapabilities A comma separated string list of integer encoded
|
||||||
|
* NetworkCapability.NET_CAPABILITY_* values
|
||||||
|
* @param overrideTransport A string representing a single integer encoded override transport
|
||||||
|
* type. Must be one of the NetworkCapability.TRANSPORT_*
|
||||||
|
* values. TRANSPORT_VPN is not supported. Errors with input
|
||||||
|
* will cause the override to be ignored.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static NetworkCapabilities.Builder createNetworkCapabilities(
|
||||||
|
boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities,
|
||||||
|
@Nullable String overrideTransport) {
|
||||||
|
|
||||||
|
final NetworkCapabilities.Builder builder = clearDefaultCapabilities
|
||||||
|
? NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||||
|
: new NetworkCapabilities.Builder();
|
||||||
|
|
||||||
|
// Determine the transport type. If someone has tried to define an override transport then
|
||||||
|
// attempt to add it. Since we can only have one override, all errors with it will
|
||||||
|
// gracefully default back to TRANSPORT_ETHERNET and warn the user. VPN is not allowed as an
|
||||||
|
// override type. Wifi Aware and LoWPAN are currently unsupported as well.
|
||||||
|
int transport = NetworkCapabilities.TRANSPORT_ETHERNET;
|
||||||
|
if (!TextUtils.isEmpty(overrideTransport)) {
|
||||||
|
try {
|
||||||
|
int parsedTransport = Integer.valueOf(overrideTransport);
|
||||||
|
if (parsedTransport == NetworkCapabilities.TRANSPORT_VPN
|
||||||
|
|| parsedTransport == NetworkCapabilities.TRANSPORT_WIFI_AWARE
|
||||||
|
|| parsedTransport == NetworkCapabilities.TRANSPORT_LOWPAN) {
|
||||||
|
Log.e(TAG, "Override transport '" + parsedTransport + "' is not supported. "
|
||||||
|
+ "Defaulting to TRANSPORT_ETHERNET");
|
||||||
|
} else {
|
||||||
|
transport = parsedTransport;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
Log.e(TAG, "Override transport type '" + overrideTransport + "' "
|
||||||
|
+ "could not be parsed. Defaulting to TRANSPORT_ETHERNET");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the transport. If the user supplied a valid number that is not a valid transport
|
||||||
|
// then adding will throw an exception. Default back to TRANSPORT_ETHERNET if that happens
|
||||||
|
try {
|
||||||
|
builder.addTransportType(transport);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
Log.e(TAG, transport + " is not a valid NetworkCapability.TRANSPORT_* value. "
|
||||||
|
+ "Defaulting to TRANSPORT_ETHERNET");
|
||||||
|
builder.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setLinkUpstreamBandwidthKbps(100 * 1000);
|
||||||
|
builder.setLinkDownstreamBandwidthKbps(100 * 1000);
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
|
||||||
|
for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
|
||||||
|
if (!TextUtils.isEmpty(strNetworkCapability)) {
|
||||||
|
try {
|
||||||
|
builder.addCapability(Integer.valueOf(strNetworkCapability));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
Log.e(TAG, "Capability '" + strNetworkCapability + "' could not be parsed");
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
Log.e(TAG, strNetworkCapability + " is not a valid "
|
||||||
|
+ "NetworkCapability.NET_CAPABILITY_* value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ethernet networks have no way to update the following capabilities, so they always
|
||||||
|
// have them.
|
||||||
|
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
|
||||||
|
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
|
||||||
|
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses static IP configuration.
|
||||||
|
*
|
||||||
|
* @param staticIpConfig represents static IP configuration in the following format: {@code
|
||||||
|
* ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
|
||||||
|
* domains=<comma-sep-domains>}
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
|
||||||
|
final StaticIpConfiguration.Builder staticIpConfigBuilder =
|
||||||
|
new StaticIpConfiguration.Builder();
|
||||||
|
|
||||||
|
for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
|
||||||
|
if (TextUtils.isEmpty(keyValueAsString)) continue;
|
||||||
|
|
||||||
|
String[] pair = keyValueAsString.split("=");
|
||||||
|
if (pair.length != 2) {
|
||||||
|
throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
|
||||||
|
+ " in " + staticIpConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = pair[0];
|
||||||
|
String value = pair[1];
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "ip":
|
||||||
|
staticIpConfigBuilder.setIpAddress(new LinkAddress(value));
|
||||||
|
break;
|
||||||
|
case "domains":
|
||||||
|
staticIpConfigBuilder.setDomains(value);
|
||||||
|
break;
|
||||||
|
case "gateway":
|
||||||
|
staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));
|
||||||
|
break;
|
||||||
|
case "dns": {
|
||||||
|
ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
|
||||||
|
for (String address: value.split(",")) {
|
||||||
|
dnsAddresses.add(InetAddress.parseNumericAddress(address));
|
||||||
|
}
|
||||||
|
staticIpConfigBuilder.setDnsServers(dnsAddresses);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default : {
|
||||||
|
throw new IllegalArgumentException("Unexpected key: " + key
|
||||||
|
+ " in " + staticIpConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createIpConfiguration(staticIpConfigBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpConfiguration createIpConfiguration(
|
||||||
|
@NonNull final StaticIpConfiguration staticIpConfig) {
|
||||||
|
return new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IpConfiguration getOrCreateIpConfiguration(String iface) {
|
||||||
|
IpConfiguration ret = mIpConfigurations.get(iface);
|
||||||
|
if (ret != null) return ret;
|
||||||
|
ret = new IpConfiguration();
|
||||||
|
ret.setIpAssignment(IpAssignment.DHCP);
|
||||||
|
ret.setProxySettings(ProxySettings.NONE);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIfaceMatchRegexp() {
|
||||||
|
final String match = mDeps.getInterfaceRegexFromResource(mContext);
|
||||||
|
mIfaceMatch = mIncludeTestInterfaces
|
||||||
|
? "(" + match + "|" + TEST_IFACE_REGEXP + ")"
|
||||||
|
: match;
|
||||||
|
Log.d(TAG, "Interface match regexp set to '" + mIfaceMatch + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if a given interface is valid for testing.
|
||||||
|
*
|
||||||
|
* @param iface the name of the interface to validate.
|
||||||
|
* @return {@code true} if test interfaces are enabled and the given {@code iface} has a test
|
||||||
|
* interface prefix, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isValidTestInterface(@NonNull final String iface) {
|
||||||
|
return mIncludeTestInterfaces && iface.matches(TEST_IFACE_REGEXP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postAndWaitForRunnable(Runnable r) {
|
||||||
|
final ConditionVariable cv = new ConditionVariable();
|
||||||
|
if (mHandler.post(() -> {
|
||||||
|
r.run();
|
||||||
|
cv.open();
|
||||||
|
})) {
|
||||||
|
cv.block(2000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting(visibility = PACKAGE)
|
||||||
|
protected void setEthernetEnabled(boolean enabled) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
int newState = enabled ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED;
|
||||||
|
if (mEthernetState == newState) return;
|
||||||
|
|
||||||
|
mEthernetState = newState;
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
trackAvailableInterfaces();
|
||||||
|
} else {
|
||||||
|
// TODO: maybe also disable server mode interface as well.
|
||||||
|
untrackFactoryInterfaces();
|
||||||
|
}
|
||||||
|
broadcastEthernetStateChange(mEthernetState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void untrackFactoryInterfaces() {
|
||||||
|
for (String iface : mFactory.getAvailableInterfaces(true /* includeRestricted */)) {
|
||||||
|
stopTrackingInterface(iface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener,
|
||||||
|
int state) {
|
||||||
|
ensureRunningOnEthernetServiceThread();
|
||||||
|
try {
|
||||||
|
listener.onEthernetStateChanged(state);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastEthernetStateChange(int state) {
|
||||||
|
ensureRunningOnEthernetServiceThread();
|
||||||
|
final int n = mListeners.beginBroadcast();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
try {
|
||||||
|
mListeners.getBroadcastItem(i).onEthernetStateChanged(state);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mListeners.finishBroadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
|
||||||
|
postAndWaitForRunnable(() -> {
|
||||||
|
pw.println(getClass().getSimpleName());
|
||||||
|
pw.println("Ethernet interface name filter: " + mIfaceMatch);
|
||||||
|
pw.println("Default interface: " + mDefaultInterface);
|
||||||
|
pw.println("Default interface mode: " + mDefaultInterfaceMode);
|
||||||
|
pw.println("Tethered interface requests: "
|
||||||
|
+ mTetheredInterfaceRequests.getRegisteredCallbackCount());
|
||||||
|
pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
|
||||||
|
pw.println("IP Configurations:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (String iface : mIpConfigurations.keySet()) {
|
||||||
|
pw.println(iface + ": " + mIpConfigurations.get(iface));
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
pw.println();
|
||||||
|
|
||||||
|
pw.println("Network Capabilities:");
|
||||||
|
pw.increaseIndent();
|
||||||
|
for (String iface : mNetworkCapabilities.keySet()) {
|
||||||
|
pw.println(iface + ": " + mNetworkCapabilities.get(iface));
|
||||||
|
}
|
||||||
|
pw.decreaseIndent();
|
||||||
|
pw.println();
|
||||||
|
|
||||||
|
mFactory.dump(fd, pw, args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static class EthernetTrackerConfig {
|
||||||
|
final String mIface;
|
||||||
|
final String mCapabilities;
|
||||||
|
final String mIpConfig;
|
||||||
|
final String mTransport;
|
||||||
|
|
||||||
|
EthernetTrackerConfig(@NonNull final String[] tokens) {
|
||||||
|
Objects.requireNonNull(tokens, "EthernetTrackerConfig requires non-null tokens");
|
||||||
|
mIface = tokens[0];
|
||||||
|
mCapabilities = tokens.length > 1 ? tokens[1] : null;
|
||||||
|
mIpConfig = tokens.length > 2 && !TextUtils.isEmpty(tokens[2]) ? tokens[2] : null;
|
||||||
|
mTransport = tokens.length > 3 ? tokens[3] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
tests/ethernet/Android.bp
Normal file
53
tests/ethernet/Android.bp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// 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 {
|
||||||
|
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: merge the tests into service-connectivity tests after
|
||||||
|
// ethernet service migration completes. So far just import the
|
||||||
|
// ethernet service source to fix the dependencies.
|
||||||
|
android_test {
|
||||||
|
name: "EthernetServiceTests",
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
":ethernet-service-updatable-sources",
|
||||||
|
":services.connectivity-ethernet-sources",
|
||||||
|
"java/**/*.java",
|
||||||
|
],
|
||||||
|
|
||||||
|
certificate: "platform",
|
||||||
|
platform_apis: true,
|
||||||
|
|
||||||
|
libs: [
|
||||||
|
"android.test.runner",
|
||||||
|
"android.test.base",
|
||||||
|
"android.test.mock",
|
||||||
|
"framework-connectivity.impl",
|
||||||
|
"framework-connectivity-t.impl",
|
||||||
|
"ServiceConnectivityResources",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"androidx.test.rules",
|
||||||
|
"frameworks-base-testutils",
|
||||||
|
"mockito-target-minus-junit4",
|
||||||
|
"net-tests-utils",
|
||||||
|
"services.core",
|
||||||
|
"services.net",
|
||||||
|
],
|
||||||
|
test_suites: ["general-tests"],
|
||||||
|
}
|
||||||
28
tests/ethernet/AndroidManifest.xml
Normal file
28
tests/ethernet/AndroidManifest.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.android.server.ethernet.tests">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<uses-library android:name="android.test.runner" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
android:targetPackage="com.android.server.ethernet.tests"
|
||||||
|
android:label="Ethernet Service Tests" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,783 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.app.test.MockAnswerUtil.AnswerWithArguments;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.EthernetNetworkSpecifier;
|
||||||
|
import android.net.EthernetNetworkManagementException;
|
||||||
|
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.net.LinkAddress;
|
||||||
|
import android.net.LinkProperties;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.NetworkAgentConfig;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.net.NetworkProvider;
|
||||||
|
import android.net.NetworkRequest;
|
||||||
|
import android.net.StaticIpConfiguration;
|
||||||
|
import android.net.ip.IpClientCallbacks;
|
||||||
|
import android.net.ip.IpClientManager;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.test.TestLooper;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.connectivity.resources.R;
|
||||||
|
import com.android.net.module.util.InterfaceParams;
|
||||||
|
|
||||||
|
import com.android.testutils.DevSdkIgnoreRule;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@SmallTest
|
||||||
|
public class EthernetNetworkFactoryTest {
|
||||||
|
private static final int TIMEOUT_MS = 2_000;
|
||||||
|
private static final String TEST_IFACE = "test123";
|
||||||
|
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
|
||||||
|
private static final String IP_ADDR = "192.0.2.2/25";
|
||||||
|
private static final LinkAddress LINK_ADDR = new LinkAddress(IP_ADDR);
|
||||||
|
private static final String HW_ADDR = "01:02:03:04:05:06";
|
||||||
|
private TestLooper mLooper;
|
||||||
|
private Handler mHandler;
|
||||||
|
private EthernetNetworkFactory mNetFactory = null;
|
||||||
|
private IpClientCallbacks mIpClientCallbacks;
|
||||||
|
@Mock private Context mContext;
|
||||||
|
@Mock private Resources mResources;
|
||||||
|
@Mock private EthernetNetworkFactory.Dependencies mDeps;
|
||||||
|
@Mock private IpClientManager mIpClient;
|
||||||
|
@Mock private EthernetNetworkAgent mNetworkAgent;
|
||||||
|
@Mock private InterfaceParams mInterfaceParams;
|
||||||
|
@Mock private Network mMockNetwork;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
setupNetworkAgentMock();
|
||||||
|
setupIpClientMock();
|
||||||
|
setupContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Move away from usage of TestLooper in order to move this logic back into @Before.
|
||||||
|
private void initEthernetNetworkFactory() {
|
||||||
|
mLooper = new TestLooper();
|
||||||
|
mHandler = new Handler(mLooper.getLooper());
|
||||||
|
mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNetworkAgentMock() {
|
||||||
|
when(mDeps.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any()))
|
||||||
|
.thenAnswer(new AnswerWithArguments() {
|
||||||
|
public EthernetNetworkAgent answer(
|
||||||
|
Context context,
|
||||||
|
Looper looper,
|
||||||
|
NetworkCapabilities nc,
|
||||||
|
LinkProperties lp,
|
||||||
|
NetworkAgentConfig config,
|
||||||
|
NetworkProvider provider,
|
||||||
|
EthernetNetworkAgent.Callbacks cb) {
|
||||||
|
when(mNetworkAgent.getCallbacks()).thenReturn(cb);
|
||||||
|
when(mNetworkAgent.getNetwork())
|
||||||
|
.thenReturn(mMockNetwork);
|
||||||
|
return mNetworkAgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupIpClientMock() throws Exception {
|
||||||
|
doAnswer(inv -> {
|
||||||
|
// these tests only support one concurrent IpClient, so make sure we do not accidentally
|
||||||
|
// create a mess.
|
||||||
|
assertNull("An IpClient has already been created.", mIpClientCallbacks);
|
||||||
|
|
||||||
|
mIpClientCallbacks = inv.getArgument(2);
|
||||||
|
mIpClientCallbacks.onIpClientCreated(null);
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
return null;
|
||||||
|
}).when(mDeps).makeIpClient(any(Context.class), anyString(), any());
|
||||||
|
|
||||||
|
doAnswer(inv -> {
|
||||||
|
mIpClientCallbacks.onQuit();
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
mIpClientCallbacks = null;
|
||||||
|
return null;
|
||||||
|
}).when(mIpClient).shutdown();
|
||||||
|
|
||||||
|
when(mDeps.makeIpClientManager(any())).thenReturn(mIpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void triggerOnProvisioningSuccess() {
|
||||||
|
mIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void triggerOnProvisioningFailure() {
|
||||||
|
mIpClientCallbacks.onProvisioningFailure(new LinkProperties());
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void triggerOnReachabilityLost() {
|
||||||
|
mIpClientCallbacks.onReachabilityLost("ReachabilityLost");
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupContext() {
|
||||||
|
when(mDeps.getTcpBufferSizesFromResource(eq(mContext))).thenReturn("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
// looper is shared with the network agents, so there may still be messages to dispatch on
|
||||||
|
// tear down.
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkCapabilities createDefaultFilterCaps() {
|
||||||
|
return NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkCapabilities.Builder createInterfaceCapsBuilder(final int transportType) {
|
||||||
|
return new NetworkCapabilities.Builder()
|
||||||
|
.addTransportType(transportType)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkRequest.Builder createDefaultRequestBuilder() {
|
||||||
|
return new NetworkRequest.Builder()
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkRequest createDefaultRequest() {
|
||||||
|
return createDefaultRequestBuilder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IpConfiguration createDefaultIpConfig() {
|
||||||
|
IpConfiguration ipConfig = new IpConfiguration();
|
||||||
|
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
||||||
|
ipConfig.setProxySettings(IpConfiguration.ProxySettings.NONE);
|
||||||
|
return ipConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an {@link IpConfiguration} with an associated {@link StaticIpConfiguration}.
|
||||||
|
*
|
||||||
|
* @return {@link IpConfiguration} with its {@link StaticIpConfiguration} set.
|
||||||
|
*/
|
||||||
|
private IpConfiguration createStaticIpConfig() {
|
||||||
|
final IpConfiguration ipConfig = new IpConfiguration();
|
||||||
|
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
|
||||||
|
ipConfig.setStaticIpConfiguration(
|
||||||
|
new StaticIpConfiguration.Builder().setIpAddress(LINK_ADDR).build());
|
||||||
|
return ipConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates an interface with provisioning in progress (since updating the interface link state
|
||||||
|
// automatically starts the provisioning process)
|
||||||
|
private void createInterfaceUndergoingProvisioning(String iface) {
|
||||||
|
// Default to the ethernet transport type.
|
||||||
|
createInterfaceUndergoingProvisioning(iface, NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createInterfaceUndergoingProvisioning(
|
||||||
|
@NonNull final String iface, final int transportType) {
|
||||||
|
final IpConfiguration ipConfig = createDefaultIpConfig();
|
||||||
|
mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
|
||||||
|
createInterfaceCapsBuilder(transportType).build());
|
||||||
|
assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
|
||||||
|
verifyStart(ipConfig);
|
||||||
|
clearInvocations(mDeps);
|
||||||
|
clearInvocations(mIpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a provisioned interface
|
||||||
|
private void createAndVerifyProvisionedInterface(String iface) throws Exception {
|
||||||
|
// Default to the ethernet transport type.
|
||||||
|
createAndVerifyProvisionedInterface(iface, NetworkCapabilities.TRANSPORT_ETHERNET,
|
||||||
|
ConnectivityManager.TYPE_ETHERNET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createVerifyAndRemoveProvisionedInterface(final int transportType,
|
||||||
|
final int expectedLegacyType) throws Exception {
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE, transportType,
|
||||||
|
expectedLegacyType);
|
||||||
|
mNetFactory.removeInterface(TEST_IFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAndVerifyProvisionedInterface(
|
||||||
|
@NonNull final String iface, final int transportType, final int expectedLegacyType)
|
||||||
|
throws Exception {
|
||||||
|
createInterfaceUndergoingProvisioning(iface, transportType);
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
// provisioning succeeded, verify that the network agent is created, registered, marked
|
||||||
|
// as connected and legacy type are correctly set.
|
||||||
|
final ArgumentCaptor<NetworkCapabilities> ncCaptor = ArgumentCaptor.forClass(
|
||||||
|
NetworkCapabilities.class);
|
||||||
|
verify(mDeps).makeEthernetNetworkAgent(any(), any(), ncCaptor.capture(), any(),
|
||||||
|
argThat(x -> x.getLegacyType() == expectedLegacyType), any(), any());
|
||||||
|
assertEquals(
|
||||||
|
new EthernetNetworkSpecifier(iface), ncCaptor.getValue().getNetworkSpecifier());
|
||||||
|
verifyNetworkAgentRegistersAndConnects();
|
||||||
|
clearInvocations(mDeps);
|
||||||
|
clearInvocations(mNetworkAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates an unprovisioned interface
|
||||||
|
private void createUnprovisionedInterface(String iface) throws Exception {
|
||||||
|
// To create an unprovisioned interface, provision and then "stop" it, i.e. stop its
|
||||||
|
// NetworkAgent and IpClient. One way this can be done is by provisioning an interface and
|
||||||
|
// then calling onNetworkUnwanted.
|
||||||
|
createAndVerifyProvisionedInterface(iface);
|
||||||
|
|
||||||
|
mNetworkAgent.getCallbacks().onNetworkUnwanted();
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
verifyStop();
|
||||||
|
|
||||||
|
clearInvocations(mIpClient);
|
||||||
|
clearInvocations(mNetworkAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptRequest() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createInterfaceUndergoingProvisioning(TEST_IFACE);
|
||||||
|
assertTrue(mNetFactory.acceptRequest(createDefaultRequest()));
|
||||||
|
|
||||||
|
NetworkRequest wifiRequest = createDefaultRequestBuilder()
|
||||||
|
.removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
|
||||||
|
assertFalse(mNetFactory.acceptRequest(wifiRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createInterfaceUndergoingProvisioning(TEST_IFACE);
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
// verify that the IpClient gets shut down when interface state changes to down.
|
||||||
|
final boolean ret =
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
|
||||||
|
|
||||||
|
assertTrue(ret);
|
||||||
|
verify(mIpClient).shutdown();
|
||||||
|
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
final boolean ret =
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
|
||||||
|
|
||||||
|
assertTrue(ret);
|
||||||
|
verifyStop();
|
||||||
|
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createUnprovisionedInterface(TEST_IFACE);
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
final boolean ret =
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
|
||||||
|
|
||||||
|
assertTrue(ret);
|
||||||
|
// There should not be an active IPClient or NetworkAgent.
|
||||||
|
verify(mDeps, never()).makeIpClient(any(), any(), any());
|
||||||
|
verify(mDeps, never())
|
||||||
|
.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
|
||||||
|
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
// if interface was never added, link state cannot be updated.
|
||||||
|
final boolean ret =
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
|
||||||
|
|
||||||
|
assertFalse(ret);
|
||||||
|
verifyNoStopOrStart();
|
||||||
|
listener.expectOnErrorWithMessage("can't be updated as it is not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
final boolean ret =
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
|
||||||
|
|
||||||
|
assertFalse(ret);
|
||||||
|
verifyNoStopOrStart();
|
||||||
|
listener.expectOnErrorWithMessage("No changes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeedNetworkForOnProvisionedInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeedNetworkForOnUnprovisionedInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createUnprovisionedInterface(TEST_IFACE);
|
||||||
|
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||||
|
verify(mIpClient).startProvisioning(any());
|
||||||
|
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
verifyNetworkAgentRegistersAndConnects();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createInterfaceUndergoingProvisioning(TEST_IFACE);
|
||||||
|
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
verifyNetworkAgentRegistersAndConnects();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvisioningLoss() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
|
||||||
|
triggerOnProvisioningFailure();
|
||||||
|
verifyStop();
|
||||||
|
// provisioning loss should trigger a retry, since the interface is still there
|
||||||
|
verify(mIpClient).startProvisioning(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProvisioningLossForDisappearedInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
// mocked method returns null by default, but just to be explicit in the test:
|
||||||
|
when(mDeps.getNetworkInterfaceByName(eq(TEST_IFACE))).thenReturn(null);
|
||||||
|
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
triggerOnProvisioningFailure();
|
||||||
|
|
||||||
|
// the interface disappeared and getNetworkInterfaceByName returns null, we should not retry
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
verifyNoStopOrStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNoStopOrStart() {
|
||||||
|
verify(mNetworkAgent, never()).register();
|
||||||
|
verify(mIpClient, never()).shutdown();
|
||||||
|
verify(mNetworkAgent, never()).unregister();
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createUnprovisionedInterface(TEST_IFACE);
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER);
|
||||||
|
|
||||||
|
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||||
|
|
||||||
|
verify(mDeps, never()).makeIpClient(any(), any(), any());
|
||||||
|
|
||||||
|
// BUG(b/191854824): requesting a network with a specifier (Android Auto use case) should
|
||||||
|
// not start an IpClient when the link is down, but fixing this may make matters worse by
|
||||||
|
// tiggering b/197548738.
|
||||||
|
NetworkRequest specificNetRequest = new NetworkRequest.Builder()
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE))
|
||||||
|
.build();
|
||||||
|
mNetFactory.needNetworkFor(specificNetRequest);
|
||||||
|
mNetFactory.releaseNetworkFor(specificNetRequest);
|
||||||
|
|
||||||
|
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER);
|
||||||
|
// TODO: change to once when b/191854824 is fixed.
|
||||||
|
verify(mDeps, times(2)).makeIpClient(any(), eq(TEST_IFACE), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLinkPropertiesChanged() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
|
||||||
|
LinkProperties lp = new LinkProperties();
|
||||||
|
mIpClientCallbacks.onLinkPropertiesChange(lp);
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
verify(mNetworkAgent).sendLinkPropertiesImpl(same(lp));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNetworkUnwanted() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
|
||||||
|
mNetworkAgent.getCallbacks().onNetworkUnwanted();
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
verifyStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNetworkUnwantedWithStaleNetworkAgent() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
// ensures provisioning is restarted after provisioning loss
|
||||||
|
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
|
||||||
|
EthernetNetworkAgent.Callbacks oldCbs = mNetworkAgent.getCallbacks();
|
||||||
|
// replace network agent in EthernetNetworkFactory
|
||||||
|
// Loss of provisioning will restart the ip client and network agent.
|
||||||
|
triggerOnProvisioningFailure();
|
||||||
|
verify(mDeps).makeIpClient(any(), any(), any());
|
||||||
|
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
verify(mDeps).makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
|
||||||
|
|
||||||
|
// verify that unwanted is ignored
|
||||||
|
clearInvocations(mIpClient);
|
||||||
|
clearInvocations(mNetworkAgent);
|
||||||
|
oldCbs.onNetworkUnwanted();
|
||||||
|
verify(mIpClient, never()).shutdown();
|
||||||
|
verify(mNetworkAgent, never()).unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransportOverrideIsCorrectlySet() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
// createProvisionedInterface() has verifications in place for transport override
|
||||||
|
// functionality which for EthernetNetworkFactory is network score and legacy type mappings.
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_ETHERNET,
|
||||||
|
ConnectivityManager.TYPE_ETHERNET);
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_BLUETOOTH,
|
||||||
|
ConnectivityManager.TYPE_BLUETOOTH);
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI,
|
||||||
|
ConnectivityManager.TYPE_WIFI);
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||||
|
ConnectivityManager.TYPE_MOBILE);
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_LOWPAN,
|
||||||
|
ConnectivityManager.TYPE_NONE);
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
|
||||||
|
ConnectivityManager.TYPE_NONE);
|
||||||
|
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_TEST,
|
||||||
|
ConnectivityManager.TYPE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReachabilityLoss() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
|
||||||
|
triggerOnReachabilityLost();
|
||||||
|
|
||||||
|
// Reachability loss should trigger a stop and start, since the interface is still there
|
||||||
|
verifyRestart(createDefaultIpConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IpClientCallbacks getStaleIpClientCallbacks() throws Exception {
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
final IpClientCallbacks staleIpClientCallbacks = mIpClientCallbacks;
|
||||||
|
mNetFactory.removeInterface(TEST_IFACE);
|
||||||
|
verifyStop();
|
||||||
|
assertNotSame(mIpClientCallbacks, staleIpClientCallbacks);
|
||||||
|
return staleIpClientCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIgnoreOnIpLayerStartedCallbackForStaleCallback() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||||
|
|
||||||
|
staleIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
verify(mNetworkAgent, never()).register();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIgnoreOnIpLayerStoppedCallbackForStaleCallback() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
|
||||||
|
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||||
|
|
||||||
|
staleIpClientCallbacks.onProvisioningFailure(new LinkProperties());
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIgnoreLinkPropertiesCallbackForStaleCallback() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||||
|
final LinkProperties lp = new LinkProperties();
|
||||||
|
|
||||||
|
staleIpClientCallbacks.onLinkPropertiesChange(lp);
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
|
||||||
|
verify(mNetworkAgent, never()).sendLinkPropertiesImpl(eq(lp));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIgnoreNeighborLossCallbackForStaleCallback() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||||
|
|
||||||
|
staleIpClientCallbacks.onReachabilityLost("Neighbor Lost");
|
||||||
|
mLooper.dispatchAll();
|
||||||
|
|
||||||
|
verify(mIpClient, never()).startProvisioning(any());
|
||||||
|
verify(mNetworkAgent, never()).register();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyRestart(@NonNull final IpConfiguration ipConfig) {
|
||||||
|
verifyStop();
|
||||||
|
verifyStart(ipConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyStart(@NonNull final IpConfiguration ipConfig) {
|
||||||
|
verify(mDeps).makeIpClient(any(Context.class), anyString(), any());
|
||||||
|
verify(mIpClient).startProvisioning(
|
||||||
|
argThat(x -> Objects.equals(x.mStaticIpConfig, ipConfig.getStaticIpConfiguration()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyStop() {
|
||||||
|
verify(mIpClient).shutdown();
|
||||||
|
verify(mNetworkAgent).unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNetworkAgentRegistersAndConnects() {
|
||||||
|
verify(mNetworkAgent).register();
|
||||||
|
verify(mNetworkAgent).markConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TestNetworkManagementListener
|
||||||
|
implements INetworkInterfaceOutcomeReceiver {
|
||||||
|
private final CompletableFuture<String> mResult = new CompletableFuture<>();
|
||||||
|
private final CompletableFuture<EthernetNetworkManagementException> mError =
|
||||||
|
new CompletableFuture<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResult(@NonNull String iface) {
|
||||||
|
mResult.complete(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull EthernetNetworkManagementException exception) {
|
||||||
|
mError.complete(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
String expectOnResult() throws Exception {
|
||||||
|
return mResult.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
EthernetNetworkManagementException expectOnError() throws Exception {
|
||||||
|
return mError.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectOnErrorWithMessage(String msg) throws Exception {
|
||||||
|
assertTrue(expectOnError().getMessage().contains(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder asBinder() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||||
|
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
|
||||||
|
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||||
|
TEST_IFACE,
|
||||||
|
() -> mNetFactory.removeInterface(TEST_IFACE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||||
|
TEST_IFACE,
|
||||||
|
() -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||||
|
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||||
|
final TestNetworkManagementListener successfulListener =
|
||||||
|
new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
// If two calls come in before the first one completes, the first listener will be aborted
|
||||||
|
// and the second one will be successful.
|
||||||
|
verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||||
|
TEST_IFACE,
|
||||||
|
() -> {
|
||||||
|
mNetFactory.updateInterface(
|
||||||
|
TEST_IFACE, ipConfiguration, capabilities, successfulListener);
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(successfulListener.expectOnResult(), TEST_IFACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||||
|
@NonNull final String iface,
|
||||||
|
@NonNull final Runnable interruptingRunnable) throws Exception {
|
||||||
|
createAndVerifyProvisionedInterface(iface);
|
||||||
|
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||||
|
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||||
|
final TestNetworkManagementListener failedListener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
// An active update request will be aborted on interrupt prior to provisioning completion.
|
||||||
|
mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener);
|
||||||
|
interruptingRunnable.run();
|
||||||
|
|
||||||
|
failedListener.expectOnErrorWithMessage("aborted");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||||
|
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
|
||||||
|
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||||
|
verify(mDeps).makeEthernetNetworkAgent(any(), any(),
|
||||||
|
eq(capabilities), any(), any(), any(), any());
|
||||||
|
verifyRestart(ipConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceForNonExistingInterface() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
// No interface exists due to not calling createAndVerifyProvisionedInterface(...).
|
||||||
|
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||||
|
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||||
|
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||||
|
|
||||||
|
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
|
||||||
|
|
||||||
|
verifyNoStopOrStart();
|
||||||
|
listener.expectOnErrorWithMessage("can't be updated as it is not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateInterfaceWithNullIpConfiguration() throws Exception {
|
||||||
|
initEthernetNetworkFactory();
|
||||||
|
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||||
|
|
||||||
|
final IpConfiguration initialIpConfig = createStaticIpConfig();
|
||||||
|
mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/,
|
||||||
|
null /*listener*/);
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
verifyRestart(initialIpConfig);
|
||||||
|
|
||||||
|
// TODO: have verifyXyz functions clear invocations.
|
||||||
|
clearInvocations(mDeps);
|
||||||
|
clearInvocations(mIpClient);
|
||||||
|
clearInvocations(mNetworkAgent);
|
||||||
|
|
||||||
|
|
||||||
|
// verify that sending a null ipConfig does not update the current ipConfig.
|
||||||
|
mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/,
|
||||||
|
null /*listener*/);
|
||||||
|
triggerOnProvisioningSuccess();
|
||||||
|
verifyRestart(initialIpConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||||
|
import android.net.EthernetNetworkUpdateRequest;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@SmallTest
|
||||||
|
public class EthernetServiceImplTest {
|
||||||
|
private static final String TEST_IFACE = "test123";
|
||||||
|
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST =
|
||||||
|
new EthernetNetworkUpdateRequest.Builder()
|
||||||
|
.setIpConfiguration(new IpConfiguration())
|
||||||
|
.setNetworkCapabilities(new NetworkCapabilities.Builder().build())
|
||||||
|
.build();
|
||||||
|
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_CAPABILITIES =
|
||||||
|
new EthernetNetworkUpdateRequest.Builder()
|
||||||
|
.setIpConfiguration(new IpConfiguration())
|
||||||
|
.build();
|
||||||
|
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_IP_CONFIG =
|
||||||
|
new EthernetNetworkUpdateRequest.Builder()
|
||||||
|
.setNetworkCapabilities(new NetworkCapabilities.Builder().build())
|
||||||
|
.build();
|
||||||
|
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
|
||||||
|
private EthernetServiceImpl mEthernetServiceImpl;
|
||||||
|
@Mock private Context mContext;
|
||||||
|
@Mock private Handler mHandler;
|
||||||
|
@Mock private EthernetTracker mEthernetTracker;
|
||||||
|
@Mock private PackageManager mPackageManager;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||||
|
mEthernetServiceImpl = new EthernetServiceImpl(mContext, mHandler, mEthernetTracker);
|
||||||
|
mEthernetServiceImpl.mStarted.set(true);
|
||||||
|
toggleAutomotiveFeature(true);
|
||||||
|
shouldTrackIface(TEST_IFACE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleAutomotiveFeature(final boolean isEnabled) {
|
||||||
|
doReturn(isEnabled)
|
||||||
|
.when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) {
|
||||||
|
doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetConfigurationRejectsWhenEthNotStarted() {
|
||||||
|
mEthernetServiceImpl.mStarted.set(false);
|
||||||
|
assertThrows(IllegalStateException.class, () -> {
|
||||||
|
mEthernetServiceImpl.setConfiguration("" /* iface */, new IpConfiguration());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationRejectsWhenEthNotStarted() {
|
||||||
|
mEthernetServiceImpl.mStarted.set(false);
|
||||||
|
assertThrows(IllegalStateException.class, () -> {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(
|
||||||
|
"" /* iface */, UPDATE_REQUEST, null /* listener */);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkRejectsWhenEthNotStarted() {
|
||||||
|
mEthernetServiceImpl.mStarted.set(false);
|
||||||
|
assertThrows(IllegalStateException.class, () -> {
|
||||||
|
mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkRejectsWhenEthNotStarted() {
|
||||||
|
mEthernetServiceImpl.mStarted.set(false);
|
||||||
|
assertThrows(IllegalStateException.class, () -> {
|
||||||
|
mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationRejectsNullIface() {
|
||||||
|
assertThrows(NullPointerException.class, () -> {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(null, UPDATE_REQUEST, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkRejectsNullIface() {
|
||||||
|
assertThrows(NullPointerException.class, () -> {
|
||||||
|
mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkRejectsNullIface() {
|
||||||
|
assertThrows(NullPointerException.class, () -> {
|
||||||
|
mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationWithCapabilitiesRejectsWithoutAutomotiveFeature() {
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationWithCapabilitiesWithAutomotiveFeature() {
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_CAPABILITIES,
|
||||||
|
NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
|
||||||
|
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getIpConfiguration()),
|
||||||
|
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkRejectsWithoutAutomotiveFeature() {
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() {
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void denyManageEthPermission() {
|
||||||
|
doThrow(new SecurityException("")).when(mContext)
|
||||||
|
.enforceCallingOrSelfPermission(
|
||||||
|
eq(Manifest.permission.MANAGE_ETHERNET_NETWORKS), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void denyManageTestNetworksPermission() {
|
||||||
|
doThrow(new SecurityException("")).when(mContext)
|
||||||
|
.enforceCallingOrSelfPermission(
|
||||||
|
eq(Manifest.permission.MANAGE_TEST_NETWORKS), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationRejectsWithoutManageEthPermission() {
|
||||||
|
denyManageEthPermission();
|
||||||
|
assertThrows(SecurityException.class, () -> {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkRejectsWithoutManageEthPermission() {
|
||||||
|
denyManageEthPermission();
|
||||||
|
assertThrows(SecurityException.class, () -> {
|
||||||
|
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkRejectsWithoutManageEthPermission() {
|
||||||
|
denyManageEthPermission();
|
||||||
|
assertThrows(SecurityException.class, () -> {
|
||||||
|
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableTestInterface() {
|
||||||
|
when(mEthernetTracker.isValidTestInterface(eq(TEST_IFACE))).thenReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationRejectsTestRequestWithoutTestPermission() {
|
||||||
|
enableTestInterface();
|
||||||
|
denyManageTestNetworksPermission();
|
||||||
|
assertThrows(SecurityException.class, () -> {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkRejectsTestRequestWithoutTestPermission() {
|
||||||
|
enableTestInterface();
|
||||||
|
denyManageTestNetworksPermission();
|
||||||
|
assertThrows(SecurityException.class, () -> {
|
||||||
|
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkRejectsTestRequestWithoutTestPermission() {
|
||||||
|
enableTestInterface();
|
||||||
|
denyManageTestNetworksPermission();
|
||||||
|
assertThrows(SecurityException.class, () -> {
|
||||||
|
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfiguration() {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).updateConfiguration(
|
||||||
|
eq(TEST_IFACE),
|
||||||
|
eq(UPDATE_REQUEST.getIpConfiguration()),
|
||||||
|
eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetwork() {
|
||||||
|
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetwork() {
|
||||||
|
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationAcceptsTestRequestWithNullCapabilities() {
|
||||||
|
enableTestInterface();
|
||||||
|
final EthernetNetworkUpdateRequest request =
|
||||||
|
new EthernetNetworkUpdateRequest
|
||||||
|
.Builder()
|
||||||
|
.setIpConfiguration(new IpConfiguration()).build();
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
|
||||||
|
eq(request.getIpConfiguration()),
|
||||||
|
eq(request.getNetworkCapabilities()), isNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationAcceptsRequestWithNullIpConfiguration() {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_IP_CONFIG,
|
||||||
|
NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
|
||||||
|
eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getIpConfiguration()),
|
||||||
|
eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()), isNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationRejectsInvalidTestRequest() {
|
||||||
|
enableTestInterface();
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private EthernetNetworkUpdateRequest createTestNetworkUpdateRequest() {
|
||||||
|
final NetworkCapabilities nc = new NetworkCapabilities
|
||||||
|
.Builder(UPDATE_REQUEST.getNetworkCapabilities())
|
||||||
|
.addTransportType(TRANSPORT_TEST).build();
|
||||||
|
|
||||||
|
return new EthernetNetworkUpdateRequest
|
||||||
|
.Builder(UPDATE_REQUEST)
|
||||||
|
.setNetworkCapabilities(nc).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfigurationForTestRequestDoesNotRequireAutoOrEthernetPermission() {
|
||||||
|
enableTestInterface();
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
denyManageEthPermission();
|
||||||
|
final EthernetNetworkUpdateRequest request = createTestNetworkUpdateRequest();
|
||||||
|
|
||||||
|
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).updateConfiguration(
|
||||||
|
eq(TEST_IFACE),
|
||||||
|
eq(request.getIpConfiguration()),
|
||||||
|
eq(request.getNetworkCapabilities()), eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
|
||||||
|
enableTestInterface();
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
denyManageEthPermission();
|
||||||
|
|
||||||
|
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
|
||||||
|
enableTestInterface();
|
||||||
|
toggleAutomotiveFeature(false);
|
||||||
|
denyManageEthPermission();
|
||||||
|
|
||||||
|
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void denyPermissions(String... permissions) {
|
||||||
|
for (String permission: permissions) {
|
||||||
|
doReturn(PackageManager.PERMISSION_DENIED).when(mContext)
|
||||||
|
.checkCallingOrSelfPermission(eq(permission));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetEthernetEnabled() {
|
||||||
|
denyPermissions(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
|
||||||
|
mEthernetServiceImpl.setEthernetEnabled(true);
|
||||||
|
verify(mEthernetTracker).setEthernetEnabled(true);
|
||||||
|
reset(mEthernetTracker);
|
||||||
|
|
||||||
|
denyPermissions(Manifest.permission.NETWORK_STACK);
|
||||||
|
mEthernetServiceImpl.setEthernetEnabled(false);
|
||||||
|
verify(mEthernetTracker).setEthernetEnabled(false);
|
||||||
|
reset(mEthernetTracker);
|
||||||
|
|
||||||
|
denyPermissions(Manifest.permission.NETWORK_SETTINGS);
|
||||||
|
try {
|
||||||
|
mEthernetServiceImpl.setEthernetEnabled(true);
|
||||||
|
fail("Should get SecurityException");
|
||||||
|
} catch (SecurityException e) { }
|
||||||
|
verify(mEthernetTracker, never()).setEthernetEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
/*
|
||||||
|
* 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.ethernet;
|
||||||
|
|
||||||
|
import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.net.EthernetManager;
|
||||||
|
import android.net.InetAddresses;
|
||||||
|
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||||
|
import android.net.IEthernetServiceListener;
|
||||||
|
import android.net.INetd;
|
||||||
|
import android.net.IpConfiguration;
|
||||||
|
import android.net.IpConfiguration.IpAssignment;
|
||||||
|
import android.net.IpConfiguration.ProxySettings;
|
||||||
|
import android.net.InterfaceConfigurationParcel;
|
||||||
|
import android.net.LinkAddress;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.net.StaticIpConfiguration;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.connectivity.resources.R;
|
||||||
|
import com.android.testutils.HandlerUtils;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class EthernetTrackerTest {
|
||||||
|
private static final String TEST_IFACE = "test123";
|
||||||
|
private static final int TIMEOUT_MS = 1_000;
|
||||||
|
private static final String THREAD_NAME = "EthernetServiceThread";
|
||||||
|
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
|
||||||
|
private EthernetTracker tracker;
|
||||||
|
private HandlerThread mHandlerThread;
|
||||||
|
@Mock private Context mContext;
|
||||||
|
@Mock private EthernetNetworkFactory mFactory;
|
||||||
|
@Mock private INetd mNetd;
|
||||||
|
@Mock private EthernetTracker.Dependencies mDeps;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws RemoteException {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
initMockResources();
|
||||||
|
when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean(), any())).thenReturn(false);
|
||||||
|
when(mNetd.interfaceGetList()).thenReturn(new String[0]);
|
||||||
|
mHandlerThread = new HandlerThread(THREAD_NAME);
|
||||||
|
mHandlerThread.start();
|
||||||
|
tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd,
|
||||||
|
mDeps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanUp() {
|
||||||
|
mHandlerThread.quitSafely();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMockResources() {
|
||||||
|
when(mDeps.getInterfaceRegexFromResource(eq(mContext))).thenReturn("");
|
||||||
|
when(mDeps.getInterfaceConfigFromResource(eq(mContext))).thenReturn(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForIdle() {
|
||||||
|
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: Creation of various valid static IP configurations
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void createStaticIpConfiguration() {
|
||||||
|
// Empty gives default StaticIPConfiguration object
|
||||||
|
assertStaticConfiguration(new StaticIpConfiguration(), "");
|
||||||
|
|
||||||
|
// Setting only the IP address properly cascades and assumes defaults
|
||||||
|
assertStaticConfiguration(new StaticIpConfiguration.Builder()
|
||||||
|
.setIpAddress(new LinkAddress("192.0.2.10/24")).build(), "ip=192.0.2.10/24");
|
||||||
|
|
||||||
|
final ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
|
||||||
|
dnsAddresses.add(InetAddresses.parseNumericAddress("4.4.4.4"));
|
||||||
|
dnsAddresses.add(InetAddresses.parseNumericAddress("8.8.8.8"));
|
||||||
|
// Setting other fields properly cascades them
|
||||||
|
assertStaticConfiguration(new StaticIpConfiguration.Builder()
|
||||||
|
.setIpAddress(new LinkAddress("192.0.2.10/24"))
|
||||||
|
.setDnsServers(dnsAddresses)
|
||||||
|
.setGateway(InetAddresses.parseNumericAddress("192.0.2.1"))
|
||||||
|
.setDomains("android").build(),
|
||||||
|
"ip=192.0.2.10/24 dns=4.4.4.4,8.8.8.8 gateway=192.0.2.1 domains=android");
|
||||||
|
|
||||||
|
// Verify order doesn't matter
|
||||||
|
assertStaticConfiguration(new StaticIpConfiguration.Builder()
|
||||||
|
.setIpAddress(new LinkAddress("192.0.2.10/24"))
|
||||||
|
.setDnsServers(dnsAddresses)
|
||||||
|
.setGateway(InetAddresses.parseNumericAddress("192.0.2.1"))
|
||||||
|
.setDomains("android").build(),
|
||||||
|
"domains=android ip=192.0.2.10/24 gateway=192.0.2.1 dns=4.4.4.4,8.8.8.8 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: Attempt creation of various bad static IP configurations
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void createStaticIpConfiguration_Bad() {
|
||||||
|
assertStaticConfigurationFails("ip=192.0.2.1/24 gateway= blah=20.20.20.20"); // Unknown key
|
||||||
|
assertStaticConfigurationFails("ip=192.0.2.1"); // mask is missing
|
||||||
|
assertStaticConfigurationFails("ip=a.b.c"); // not a valid ip address
|
||||||
|
assertStaticConfigurationFails("dns=4.4.4.4,1.2.3.A"); // not valid ip address in dns
|
||||||
|
assertStaticConfigurationFails("="); // Key and value is empty
|
||||||
|
assertStaticConfigurationFails("ip="); // Value is empty
|
||||||
|
assertStaticConfigurationFails("ip=192.0.2.1/24 gateway="); // Gateway is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStaticConfigurationFails(String config) {
|
||||||
|
try {
|
||||||
|
EthernetTracker.parseStaticIpConfiguration(config);
|
||||||
|
fail("Expected to fail: " + config);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStaticConfiguration(StaticIpConfiguration expectedStaticIpConfig,
|
||||||
|
String configAsString) {
|
||||||
|
final IpConfiguration expectedIpConfiguration = new IpConfiguration();
|
||||||
|
expectedIpConfiguration.setIpAssignment(IpAssignment.STATIC);
|
||||||
|
expectedIpConfiguration.setProxySettings(ProxySettings.NONE);
|
||||||
|
expectedIpConfiguration.setStaticIpConfiguration(expectedStaticIpConfig);
|
||||||
|
|
||||||
|
assertEquals(expectedIpConfiguration,
|
||||||
|
EthernetTracker.parseStaticIpConfiguration(configAsString));
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkCapabilities.Builder makeEthernetCapabilitiesBuilder(boolean clearAll) {
|
||||||
|
final NetworkCapabilities.Builder builder =
|
||||||
|
clearAll ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||||
|
: new NetworkCapabilities.Builder();
|
||||||
|
return builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: Attempt to create a capabilties with various valid sets of capabilities/transports
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void createNetworkCapabilities() {
|
||||||
|
|
||||||
|
// Particularly common expected results
|
||||||
|
NetworkCapabilities defaultEthernetCleared =
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
NetworkCapabilities ethernetClearedWithCommonCaps =
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.addCapability(12)
|
||||||
|
.addCapability(13)
|
||||||
|
.addCapability(14)
|
||||||
|
.addCapability(15)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Empty capabilities and transports lists with a "please clear defaults" should
|
||||||
|
// yield an empty capabilities set with TRANPORT_ETHERNET
|
||||||
|
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "");
|
||||||
|
|
||||||
|
// Empty capabilities and transports without the clear defaults flag should return the
|
||||||
|
// default capabilities set with TRANSPORT_ETHERNET
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(false /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.build(),
|
||||||
|
false, "", "");
|
||||||
|
|
||||||
|
// A list of capabilities without the clear defaults flag should return the default
|
||||||
|
// capabilities, mixed with the desired capabilities, and TRANSPORT_ETHERNET
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(false /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||||
|
.addCapability(11)
|
||||||
|
.addCapability(12)
|
||||||
|
.build(),
|
||||||
|
false, "11,12", "");
|
||||||
|
|
||||||
|
// Adding a list of capabilities with a clear defaults will leave exactly those capabilities
|
||||||
|
// with a default TRANSPORT_ETHERNET since no overrides are specified
|
||||||
|
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15", "");
|
||||||
|
|
||||||
|
// Adding any invalid capabilities to the list will cause them to be ignored
|
||||||
|
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,65,73", "");
|
||||||
|
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,abcdefg", "");
|
||||||
|
|
||||||
|
// Adding a valid override transport will remove the default TRANSPORT_ETHERNET transport
|
||||||
|
// and apply only the override to the capabiltities object
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(0)
|
||||||
|
.build(),
|
||||||
|
true, "", "0");
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(1)
|
||||||
|
.build(),
|
||||||
|
true, "", "1");
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(2)
|
||||||
|
.build(),
|
||||||
|
true, "", "2");
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addTransportType(3)
|
||||||
|
.build(),
|
||||||
|
true, "", "3");
|
||||||
|
|
||||||
|
// "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANPORT_ETHERNET
|
||||||
|
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "4");
|
||||||
|
|
||||||
|
// "5" is TRANSPORT_WIFI_AWARE, which is currently supported due to no legacy TYPE_NONE
|
||||||
|
// conversion. When that becomes available, this test must be updated
|
||||||
|
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "5");
|
||||||
|
|
||||||
|
// "6" is TRANSPORT_LOWPAN, which is currently supported due to no legacy TYPE_NONE
|
||||||
|
// conversion. When that becomes available, this test must be updated
|
||||||
|
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "6");
|
||||||
|
|
||||||
|
// Adding an invalid override transport will leave the transport as TRANSPORT_ETHERNET
|
||||||
|
assertParsedNetworkCapabilities(defaultEthernetCleared,true, "", "100");
|
||||||
|
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "abcdefg");
|
||||||
|
|
||||||
|
// Ensure the adding of both capabilities and transports work
|
||||||
|
assertParsedNetworkCapabilities(
|
||||||
|
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||||
|
.setLinkUpstreamBandwidthKbps(100000)
|
||||||
|
.setLinkDownstreamBandwidthKbps(100000)
|
||||||
|
.addCapability(12)
|
||||||
|
.addCapability(13)
|
||||||
|
.addCapability(14)
|
||||||
|
.addCapability(15)
|
||||||
|
.addTransportType(3)
|
||||||
|
.build(),
|
||||||
|
true, "12,13,14,15", "3");
|
||||||
|
|
||||||
|
// Ensure order does not matter for capability list
|
||||||
|
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "13,12,15,14", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertParsedNetworkCapabilities(NetworkCapabilities expectedNetworkCapabilities,
|
||||||
|
boolean clearCapabilties, String configCapabiltiies,String configTransports) {
|
||||||
|
assertEquals(expectedNetworkCapabilities,
|
||||||
|
EthernetTracker.createNetworkCapabilities(clearCapabilties, configCapabiltiies,
|
||||||
|
configTransports).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateEthernetTrackerConfigReturnsCorrectValue() {
|
||||||
|
final String capabilities = "2";
|
||||||
|
final String ipConfig = "3";
|
||||||
|
final String transport = "4";
|
||||||
|
final String configString = String.join(";", TEST_IFACE, capabilities, ipConfig, transport);
|
||||||
|
|
||||||
|
final EthernetTracker.EthernetTrackerConfig config =
|
||||||
|
EthernetTracker.createEthernetTrackerConfig(configString);
|
||||||
|
|
||||||
|
assertEquals(TEST_IFACE, config.mIface);
|
||||||
|
assertEquals(capabilities, config.mCapabilities);
|
||||||
|
assertEquals(ipConfig, config.mIpConfig);
|
||||||
|
assertEquals(transport, config.mTransport);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateEthernetTrackerConfigThrowsNpeWithNullInput() {
|
||||||
|
assertThrows(NullPointerException.class,
|
||||||
|
() -> EthernetTracker.createEthernetTrackerConfig(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateConfiguration() {
|
||||||
|
final NetworkCapabilities capabilities = new NetworkCapabilities.Builder().build();
|
||||||
|
final LinkAddress linkAddr = new LinkAddress("192.0.2.2/25");
|
||||||
|
final StaticIpConfiguration staticIpConfig =
|
||||||
|
new StaticIpConfiguration.Builder().setIpAddress(linkAddr).build();
|
||||||
|
final IpConfiguration ipConfig =
|
||||||
|
new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
|
||||||
|
final INetworkInterfaceOutcomeReceiver listener = null;
|
||||||
|
|
||||||
|
tracker.updateConfiguration(TEST_IFACE, ipConfig, capabilities, listener);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
verify(mFactory).updateInterface(
|
||||||
|
eq(TEST_IFACE), eq(ipConfig), eq(capabilities), eq(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectNetworkCorrectlyCallsFactory() {
|
||||||
|
tracker.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
|
||||||
|
eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisconnectNetworkCorrectlyCallsFactory() {
|
||||||
|
tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
|
||||||
|
eq(NULL_LISTENER));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsValidTestInterfaceIsFalseWhenTestInterfacesAreNotIncluded() {
|
||||||
|
final String validIfaceName = TEST_TAP_PREFIX + "123";
|
||||||
|
tracker.setIncludeTestInterfaces(false);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName);
|
||||||
|
|
||||||
|
assertFalse(isValidTestInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsValidTestInterfaceIsFalseWhenTestInterfaceNameIsInvalid() {
|
||||||
|
final String invalidIfaceName = "123" + TEST_TAP_PREFIX;
|
||||||
|
tracker.setIncludeTestInterfaces(true);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
final boolean isValidTestInterface = tracker.isValidTestInterface(invalidIfaceName);
|
||||||
|
|
||||||
|
assertFalse(isValidTestInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsValidTestInterfaceIsTrueWhenTestInterfacesIncludedAndValidName() {
|
||||||
|
final String validIfaceName = TEST_TAP_PREFIX + "123";
|
||||||
|
tracker.setIncludeTestInterfaces(true);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName);
|
||||||
|
|
||||||
|
assertTrue(isValidTestInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EthernetStateListener extends IEthernetServiceListener.Stub {
|
||||||
|
@Override
|
||||||
|
public void onEthernetStateChanged(int state) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInterfaceStateChanged(String iface, int state, int role,
|
||||||
|
IpConfiguration configuration) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testListenEthernetStateChange() throws Exception {
|
||||||
|
final String testIface = "testtap123";
|
||||||
|
final String testHwAddr = "11:22:33:44:55:66";
|
||||||
|
final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
|
||||||
|
ifaceParcel.ifName = testIface;
|
||||||
|
ifaceParcel.hwAddr = testHwAddr;
|
||||||
|
ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
|
||||||
|
|
||||||
|
tracker.setIncludeTestInterfaces(true);
|
||||||
|
waitForIdle();
|
||||||
|
|
||||||
|
when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
|
||||||
|
when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
|
||||||
|
doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
|
||||||
|
doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
|
||||||
|
|
||||||
|
final EthernetStateListener listener = spy(new EthernetStateListener());
|
||||||
|
tracker.addListener(listener, true /* canUseRestrictedNetworks */);
|
||||||
|
// Check default state.
|
||||||
|
waitForIdle();
|
||||||
|
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
|
||||||
|
anyInt(), any());
|
||||||
|
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
|
||||||
|
reset(listener);
|
||||||
|
|
||||||
|
doReturn(EthernetManager.STATE_ABSENT).when(mFactory).getInterfaceState(eq(testIface));
|
||||||
|
tracker.setEthernetEnabled(false);
|
||||||
|
waitForIdle();
|
||||||
|
verify(mFactory).removeInterface(eq(testIface));
|
||||||
|
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_DISABLED));
|
||||||
|
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_ABSENT),
|
||||||
|
anyInt(), any());
|
||||||
|
reset(listener);
|
||||||
|
|
||||||
|
doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
|
||||||
|
tracker.setEthernetEnabled(true);
|
||||||
|
waitForIdle();
|
||||||
|
verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
|
||||||
|
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
|
||||||
|
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
|
||||||
|
anyInt(), any());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user