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:
Remi NGUYEN VAN
2022-03-31 11:35:01 +09:00
11 changed files with 3918 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}
}