| /* |
| * 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; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.INetd; |
| import android.net.ITestNetworkManager; |
| import android.net.IpPrefix; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.NetworkAgent; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.NetworkInfo.DetailedState; |
| import android.net.RouteInfo; |
| import android.net.StringNetworkSpecifier; |
| import android.net.TestNetworkInterface; |
| import android.net.util.NetdService; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.INetworkManagementService; |
| import android.os.Looper; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.UncheckedIOException; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InterfaceAddress; |
| import java.net.NetworkInterface; |
| import java.net.SocketException; |
| import java.util.ArrayList; |
| import java.util.Objects; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** @hide */ |
| class TestNetworkService extends ITestNetworkManager.Stub { |
| @NonNull private static final String TAG = TestNetworkService.class.getSimpleName(); |
| @NonNull private static final String TEST_NETWORK_TYPE = "TEST_NETWORK"; |
| @NonNull private static final String TEST_TUN_PREFIX = "testtun"; |
| @NonNull private static final String TEST_TAP_PREFIX = "testtap"; |
| @NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger(); |
| |
| @NonNull private final Context mContext; |
| @NonNull private final INetworkManagementService mNMS; |
| @NonNull private final INetd mNetd; |
| |
| @NonNull private final HandlerThread mHandlerThread; |
| @NonNull private final Handler mHandler; |
| |
| // Native method stubs |
| private static native int jniCreateTunTap(boolean isTun, @NonNull String iface); |
| |
| @VisibleForTesting |
| protected TestNetworkService( |
| @NonNull Context context, @NonNull INetworkManagementService netManager) { |
| mHandlerThread = new HandlerThread("TestNetworkServiceThread"); |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper()); |
| |
| mContext = Objects.requireNonNull(context, "missing Context"); |
| mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService"); |
| mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance"); |
| } |
| |
| /** |
| * Create a TUN or TAP interface with the given interface name and link addresses |
| * |
| * <p>This method will return the FileDescriptor to the interface. Close it to tear down the |
| * interface. |
| */ |
| private TestNetworkInterface createInterface(boolean isTun, LinkAddress[] linkAddrs) { |
| enforceTestNetworkPermissions(mContext); |
| |
| Objects.requireNonNull(linkAddrs, "missing linkAddrs"); |
| |
| String ifacePrefix = isTun ? TEST_TUN_PREFIX : TEST_TAP_PREFIX; |
| String iface = ifacePrefix + sTestTunIndex.getAndIncrement(); |
| return Binder.withCleanCallingIdentity( |
| () -> { |
| try { |
| ParcelFileDescriptor tunIntf = |
| ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, iface)); |
| for (LinkAddress addr : linkAddrs) { |
| mNetd.interfaceAddAddress( |
| iface, |
| addr.getAddress().getHostAddress(), |
| addr.getPrefixLength()); |
| } |
| |
| return new TestNetworkInterface(tunIntf, iface); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| }); |
| } |
| |
| /** |
| * Create a TUN interface with the given interface name and link addresses |
| * |
| * <p>This method will return the FileDescriptor to the TUN interface. Close it to tear down the |
| * TUN interface. |
| */ |
| @Override |
| public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { |
| return createInterface(true, linkAddrs); |
| } |
| |
| /** |
| * Create a TAP interface with the given interface name |
| * |
| * <p>This method will return the FileDescriptor to the TAP interface. Close it to tear down the |
| * TAP interface. |
| */ |
| @Override |
| public TestNetworkInterface createTapInterface() { |
| return createInterface(false, new LinkAddress[0]); |
| } |
| |
| // Tracker for TestNetworkAgents |
| @GuardedBy("mTestNetworkTracker") |
| @NonNull |
| private final SparseArray<TestNetworkAgent> mTestNetworkTracker = new SparseArray<>(); |
| |
| public class TestNetworkAgent extends NetworkAgent implements IBinder.DeathRecipient { |
| private static final int NETWORK_SCORE = 1; // Use a low, non-zero score. |
| |
| private final int mUid; |
| @NonNull private final NetworkInfo mNi; |
| @NonNull private final NetworkCapabilities mNc; |
| @NonNull private final LinkProperties mLp; |
| |
| @GuardedBy("mBinderLock") |
| @NonNull |
| private IBinder mBinder; |
| |
| @NonNull private final Object mBinderLock = new Object(); |
| |
| private TestNetworkAgent( |
| @NonNull Looper looper, |
| @NonNull Context context, |
| @NonNull NetworkInfo ni, |
| @NonNull NetworkCapabilities nc, |
| @NonNull LinkProperties lp, |
| int uid, |
| @NonNull IBinder binder) |
| throws RemoteException { |
| super(looper, context, TEST_NETWORK_TYPE, ni, nc, lp, NETWORK_SCORE); |
| |
| mUid = uid; |
| mNi = ni; |
| mNc = nc; |
| mLp = lp; |
| |
| synchronized (mBinderLock) { |
| mBinder = binder; // Binder null-checks in create() |
| |
| try { |
| mBinder.linkToDeath(this, 0); |
| } catch (RemoteException e) { |
| binderDied(); |
| throw e; // Abort, signal failure up the stack. |
| } |
| } |
| } |
| |
| /** |
| * If the Binder object dies, this function is called to free the resources of this |
| * TestNetworkAgent |
| */ |
| @Override |
| public void binderDied() { |
| teardown(); |
| } |
| |
| @Override |
| protected void unwanted() { |
| teardown(); |
| } |
| |
| private void teardown() { |
| mNi.setDetailedState(DetailedState.DISCONNECTED, null, null); |
| mNi.setIsAvailable(false); |
| sendNetworkInfo(mNi); |
| |
| // Synchronize on mBinderLock to ensure that unlinkToDeath is never called more than |
| // once (otherwise it could throw an exception) |
| synchronized (mBinderLock) { |
| // If mBinder is null, this Test Network has already been cleaned up. |
| if (mBinder == null) return; |
| mBinder.unlinkToDeath(this, 0); |
| mBinder = null; |
| } |
| |
| // Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up |
| // resources, even for binder death or unwanted calls. |
| synchronized (mTestNetworkTracker) { |
| mTestNetworkTracker.remove(netId); |
| } |
| } |
| } |
| |
| private TestNetworkAgent registerTestNetworkAgent( |
| @NonNull Looper looper, |
| @NonNull Context context, |
| @NonNull String iface, |
| @Nullable LinkProperties lp, |
| boolean isMetered, |
| int callingUid, |
| @NonNull IBinder binder) |
| throws RemoteException, SocketException { |
| Objects.requireNonNull(looper, "missing Looper"); |
| Objects.requireNonNull(context, "missing Context"); |
| // iface and binder validity checked by caller |
| |
| // Build network info with special testing type |
| NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_TEST, 0, TEST_NETWORK_TYPE, ""); |
| ni.setDetailedState(DetailedState.CONNECTED, null, null); |
| ni.setIsAvailable(true); |
| |
| // Build narrow set of NetworkCapabilities, useful only for testing |
| NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.clearAll(); // Remove default capabilities. |
| nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); |
| nc.setNetworkSpecifier(new StringNetworkSpecifier(iface)); |
| if (!isMetered) { |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); |
| } |
| |
| // Build LinkProperties |
| if (lp == null) { |
| lp = new LinkProperties(); |
| } else { |
| lp = new LinkProperties(lp); |
| // Use LinkAddress(es) from the interface itself to minimize how much the caller |
| // is trusted. |
| lp.setLinkAddresses(new ArrayList<>()); |
| } |
| lp.setInterfaceName(iface); |
| |
| // Find the currently assigned addresses, and add them to LinkProperties |
| boolean allowIPv4 = false, allowIPv6 = false; |
| NetworkInterface netIntf = NetworkInterface.getByName(iface); |
| Objects.requireNonNull(netIntf, "No such network interface found: " + netIntf); |
| |
| for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) { |
| lp.addLinkAddress( |
| new LinkAddress(intfAddr.getAddress(), intfAddr.getNetworkPrefixLength())); |
| |
| if (intfAddr.getAddress() instanceof Inet6Address) { |
| allowIPv6 |= !intfAddr.getAddress().isLinkLocalAddress(); |
| } else if (intfAddr.getAddress() instanceof Inet4Address) { |
| allowIPv4 = true; |
| } |
| } |
| |
| // Add global routes (but as non-default, non-internet providing network) |
| if (allowIPv4) { |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null, iface)); |
| } |
| if (allowIPv6) { |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null, iface)); |
| } |
| |
| return new TestNetworkAgent(looper, context, ni, nc, lp, callingUid, binder); |
| } |
| |
| /** |
| * Sets up a Network with extremely limited privileges, guarded by the MANAGE_TEST_NETWORKS |
| * permission. |
| * |
| * <p>This method provides a Network that is useful only for testing. |
| */ |
| @Override |
| public void setupTestNetwork( |
| @NonNull String iface, |
| @Nullable LinkProperties lp, |
| boolean isMetered, |
| @NonNull IBinder binder) { |
| enforceTestNetworkPermissions(mContext); |
| |
| Objects.requireNonNull(iface, "missing Iface"); |
| Objects.requireNonNull(binder, "missing IBinder"); |
| |
| if (!(iface.startsWith(INetd.IPSEC_INTERFACE_PREFIX) |
| || iface.startsWith(TEST_TUN_PREFIX))) { |
| throw new IllegalArgumentException( |
| "Cannot create network for non ipsec, non-testtun interface"); |
| } |
| |
| // Setup needs to be done with NETWORK_STACK privileges. |
| int callingUid = Binder.getCallingUid(); |
| Binder.withCleanCallingIdentity( |
| () -> { |
| try { |
| mNMS.setInterfaceUp(iface); |
| |
| // Synchronize all accesses to mTestNetworkTracker to prevent the case |
| // where: |
| // 1. TestNetworkAgent successfully binds to death of binder |
| // 2. Before it is added to the mTestNetworkTracker, binder dies, |
| // binderDied() is called (on a different thread) |
| // 3. This thread is pre-empted, put() is called after remove() |
| synchronized (mTestNetworkTracker) { |
| TestNetworkAgent agent = |
| registerTestNetworkAgent( |
| mHandler.getLooper(), |
| mContext, |
| iface, |
| lp, |
| isMetered, |
| callingUid, |
| binder); |
| |
| mTestNetworkTracker.put(agent.netId, agent); |
| } |
| } catch (SocketException e) { |
| throw new UncheckedIOException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| }); |
| } |
| |
| /** Teardown a test network */ |
| @Override |
| public void teardownTestNetwork(int netId) { |
| enforceTestNetworkPermissions(mContext); |
| |
| final TestNetworkAgent agent; |
| synchronized (mTestNetworkTracker) { |
| agent = mTestNetworkTracker.get(netId); |
| } |
| |
| if (agent == null) { |
| return; // Already torn down |
| } else if (agent.mUid != Binder.getCallingUid()) { |
| throw new SecurityException("Attempted to modify other user's test networks"); |
| } |
| |
| // Safe to be called multiple times. |
| agent.teardown(); |
| } |
| |
| private static final String PERMISSION_NAME = |
| android.Manifest.permission.MANAGE_TEST_NETWORKS; |
| |
| public static void enforceTestNetworkPermissions(@NonNull Context context) { |
| context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService"); |
| } |
| } |