content: /*
   * 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 static com.android.internal.util.Preconditions.checkNotNull;
  
  import android.annotation.NonNull;
  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.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 = checkNotNull(context, "missing Context");
          mNMS = checkNotNull(netManager, "missing INetworkManagementService");
          mNetd = checkNotNull(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);
  
          checkNotNull(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,
              int callingUid,
              @NonNull IBinder binder)
              throws RemoteException, SocketException {
          checkNotNull(looper, "missing Looper");
          checkNotNull(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));
  
          // Build LinkProperties
          LinkProperties lp = new LinkProperties();
          lp.setInterfaceName(iface);
  
          // Find the currently assigned addresses, and add them to LinkProperties
          boolean allowIPv4 = false, allowIPv6 = false;
          NetworkInterface netIntf = NetworkInterface.getByName(iface);
          checkNotNull(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, @NonNull IBinder binder) {
          enforceTestNetworkPermissions(mContext);
  
          checkNotNull(iface, "missing Iface");
          checkNotNull(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,
                                              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");
      }
  }
  