summaryrefslogtreecommitdiff
path: root/service-t/src
diff options
context:
space:
mode:
Diffstat (limited to 'service-t/src')
-rw-r--r--service-t/src/com/android/server/ConnectivityServiceInitializer.java22
-rw-r--r--service-t/src/com/android/server/ethernet/EthernetConfigStore.java88
-rw-r--r--service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java65
-rw-r--r--service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java784
-rw-r--r--service-t/src/com/android/server/ethernet/EthernetService.java47
-rw-r--r--service-t/src/com/android/server/ethernet/EthernetServiceImpl.java299
-rw-r--r--service-t/src/com/android/server/ethernet/EthernetTracker.java942
-rw-r--r--service-t/src/com/android/server/net/DelayedDiskWrite.java114
8 files changed, 2247 insertions, 114 deletions
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index fa86f3965a..e4efa9261b 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -21,6 +21,7 @@ import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
import com.android.networkstack.apishim.ConstantsShim;
+import com.android.server.connectivity.ConnectivityNativeService;
import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
@@ -31,6 +32,7 @@ import com.android.server.nearby.NearbyService;
*/
public final class ConnectivityServiceInitializer extends SystemService {
private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
+ private final ConnectivityNativeService mConnectivityNative;
private final ConnectivityService mConnectivity;
private final IpSecService mIpSecService;
private final NsdService mNsdService;
@@ -44,6 +46,7 @@ public final class ConnectivityServiceInitializer extends SystemService {
mEthernetServiceImpl = createEthernetService(context);
mConnectivity = new ConnectivityService(context);
mIpSecService = createIpSecService(context);
+ mConnectivityNative = createConnectivityNativeService(context);
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
}
@@ -65,6 +68,12 @@ public final class ConnectivityServiceInitializer extends SystemService {
publishBinderService(Context.IPSEC_SERVICE, mIpSecService, /* allowIsolated= */ false);
}
+ if (mConnectivityNative != null) {
+ Log.i(TAG, "Registering " + ConnectivityNativeService.SERVICE_NAME);
+ publishBinderService(ConnectivityNativeService.SERVICE_NAME, mConnectivityNative,
+ /* allowIsolated= */ false);
+ }
+
if (mNsdService != null) {
Log.i(TAG, "Registering " + Context.NSD_SERVICE);
publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
@@ -98,6 +107,19 @@ public final class ConnectivityServiceInitializer extends SystemService {
return new IpSecService(context);
}
+ /**
+ * Return ConnectivityNativeService instance, or null if current SDK is lower than T.
+ */
+ private ConnectivityNativeService createConnectivityNativeService(final Context context) {
+ if (!SdkLevel.isAtLeastT()) return null;
+ try {
+ return new ConnectivityNativeService(context);
+ } catch (UnsupportedOperationException e) {
+ Log.d(TAG, "Unable to get ConnectivityNative service", e);
+ return null;
+ }
+ }
+
/** Return NsdService instance or null if current SDK is lower than T */
private NsdService createNsdService(final Context context) {
if (!SdkLevel.isAtLeastT()) return null;
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
new file mode 100644
index 0000000000..6b623f48ff
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -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);
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java b/service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java
new file mode 100644
index 0000000000..57fbce7e86
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkAgent.java
@@ -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;
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
new file mode 100644
index 0000000000..eb22f7875b
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -0,0 +1,784 @@
+/*
+ * 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.EthernetNetworkManagementException;
+import android.net.EthernetNetworkSpecifier;
+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.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();
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetService.java b/service-t/src/com/android/server/ethernet/EthernetService.java
new file mode 100644
index 0000000000..d405fd59fb
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetService.java
@@ -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)));
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
new file mode 100644
index 0000000000..5e830ad83a
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -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();
+ }
+}
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
new file mode 100644
index 0000000000..c291b3ff66
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -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;
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/net/DelayedDiskWrite.java b/service-t/src/com/android/server/net/DelayedDiskWrite.java
deleted file mode 100644
index 35dc455725..0000000000
--- a/service-t/src/com/android/server/net/DelayedDiskWrite.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.net;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.BufferedOutputStream;
-import java.io.DataOutputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * This class provides APIs to do a delayed data write to a given {@link OutputStream}.
- */
-public class DelayedDiskWrite {
- private static final String TAG = "DelayedDiskWrite";
-
- private HandlerThread mDiskWriteHandlerThread;
- private Handler mDiskWriteHandler;
- /* Tracks multiple writes on the same thread */
- private int mWriteSequence = 0;
-
- /**
- * Used to do a delayed data write to a given {@link OutputStream}.
- */
- public interface Writer {
- /**
- * write data to a given {@link OutputStream}.
- */
- void onWriteCalled(DataOutputStream out) throws IOException;
- }
-
- /**
- * Do a delayed data write to a given output stream opened from filePath.
- */
- public void write(final String filePath, final Writer w) {
- write(filePath, w, true);
- }
-
- /**
- * Do a delayed data write to a given output stream opened from filePath.
- */
- public void write(final String filePath, final Writer w, final boolean open) {
- if (TextUtils.isEmpty(filePath)) {
- throw new IllegalArgumentException("empty file path");
- }
-
- /* Do a delayed write to disk on a separate handler thread */
- synchronized (this) {
- if (++mWriteSequence == 1) {
- mDiskWriteHandlerThread = new HandlerThread("DelayedDiskWriteThread");
- mDiskWriteHandlerThread.start();
- mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper());
- }
- }
-
- mDiskWriteHandler.post(new Runnable() {
- @Override
- public void run() {
- doWrite(filePath, w, open);
- }
- });
- }
-
- private void doWrite(String filePath, Writer w, boolean open) {
- DataOutputStream out = null;
- try {
- if (open) {
- out = new DataOutputStream(new BufferedOutputStream(
- new FileOutputStream(filePath)));
- }
- w.onWriteCalled(out);
- } catch (IOException e) {
- loge("Error writing data file " + filePath);
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (Exception e) { }
- }
-
- // Quit if no more writes sent
- synchronized (this) {
- if (--mWriteSequence == 0) {
- mDiskWriteHandler.getLooper().quit();
- mDiskWriteHandler = null;
- mDiskWriteHandlerThread = null;
- }
- }
- }
- }
-
- private void loge(String s) {
- Log.e(TAG, s);
- }
-}
-