/* * 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 android.net.TestNetworkManager.CLAT_INTERFACE_PREFIX; import static android.net.TestNetworkManager.TEST_TAP_PREFIX; import static android.net.TestNetworkManager.TEST_TUN_PREFIX; 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.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.RouteInfo; import android.net.TestNetworkInterface; import android.net.TestNetworkSpecifier; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; 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 com.android.net.module.util.NetworkStackConstants; import java.io.IOException; 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 TEST_NETWORK_LOGTAG = "TestNetworkAgent"; @NonNull private static final String TEST_NETWORK_PROVIDER_NAME = "TestNetworkProvider"; @NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger(); @NonNull private final Context mContext; @NonNull private final INetd mNetd; @NonNull private final HandlerThread mHandlerThread; @NonNull private final Handler mHandler; @NonNull private final ConnectivityManager mCm; @NonNull private final NetworkProvider mNetworkProvider; // Native method stubs private static native int nativeCreateTunTap(boolean isTun, boolean hasCarrier, boolean setIffMulticast, @NonNull String iface); private static native void nativeSetTunTapCarrierEnabled(@NonNull String iface, int tunFd, boolean enabled); private static native void nativeBringUpInterface(String iface); @VisibleForTesting protected TestNetworkService(@NonNull Context context) { mHandlerThread = new HandlerThread("TestNetworkServiceThread"); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mContext = Objects.requireNonNull(context, "missing Context"); mNetd = Objects.requireNonNull( INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), "could not get netd instance"); mCm = mContext.getSystemService(ConnectivityManager.class); mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(), TEST_NETWORK_PROVIDER_NAME); final long token = Binder.clearCallingIdentity(); try { mCm.registerNetworkProvider(mNetworkProvider); } finally { Binder.restoreCallingIdentity(token); } } // TODO: find a way to allow the caller to pass in non-clat interface names, ensuring that // those names do not conflict with names created by callers that do not pass in an interface // name. private static boolean isValidInterfaceName(@NonNull final String iface) { return iface.startsWith(CLAT_INTERFACE_PREFIX + TEST_TUN_PREFIX) || iface.startsWith(CLAT_INTERFACE_PREFIX + TEST_TAP_PREFIX); } /** * Create a TUN or TAP interface with the specified parameters. * *
This method will return the FileDescriptor to the interface. Close it to tear down the
     * interface.
     */
    @Override
    public TestNetworkInterface createInterface(boolean isTun, boolean hasCarrier, boolean bringUp,
            boolean disableIpv6ProvisioningDelay, LinkAddress[] linkAddrs, @Nullable String iface) {
        enforceTestNetworkPermissions(mContext);
        Objects.requireNonNull(linkAddrs, "missing linkAddrs");
        String interfaceName = iface;
        if (iface == null) {
            String ifacePrefix = isTun ? TEST_TUN_PREFIX : TEST_TAP_PREFIX;
            interfaceName = ifacePrefix + sTestTunIndex.getAndIncrement();
        } else if (!isValidInterfaceName(iface)) {
            throw new IllegalArgumentException("invalid interface name requested: " + iface);
        }
        final long token = Binder.clearCallingIdentity();
        try {
            // Note: if the interface is brought up by ethernet, setting IFF_MULTICAST
            // races NetUtils#setInterfaceUp(). This flag is not necessary for ethernet
            // tests, so let's not set it when bringUp is false. See also b/242343156.
            // In the future, we could use RTM_SETLINK with ifi_change set to set the
            // flags atomically.
            final boolean setIffMulticast = bringUp;
            ParcelFileDescriptor tunIntf = ParcelFileDescriptor.adoptFd(
                    nativeCreateTunTap(isTun, hasCarrier, setIffMulticast, interfaceName));
            // Disable DAD and remove router_solicitation_delay before assigning link addresses.
            if (disableIpv6ProvisioningDelay) {
                mNetd.setProcSysNet(
                        INetd.IPV6, INetd.CONF, interfaceName, "router_solicitation_delay", "0");
                mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, interfaceName, "dad_transmits", "0");
            }
            for (LinkAddress addr : linkAddrs) {
                mNetd.interfaceAddAddress(
                        interfaceName,
                        addr.getAddress().getHostAddress(),
                        addr.getPrefixLength());
            }
            if (bringUp) {
                nativeBringUpInterface(interfaceName);
            }
            return new TestNetworkInterface(tunIntf, interfaceName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }
    // Tracker for TestNetworkAgents
    @GuardedBy("mTestNetworkTracker")
    @NonNull
    private final SparseArray This method provides a Network that is useful only for testing.
     */
    @Override
    public void setupTestNetwork(
            @NonNull String iface,
            @Nullable LinkProperties lp,
            boolean isMetered,
            @NonNull int[] administratorUids,
            @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");
        }
        try {
            // 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,
                                Binder.getCallingUid(),
                                administratorUids,
                                binder);
                mTestNetworkTracker.put(agent.getNetwork().getNetId(), 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");
    }
    /** Enable / disable TestNetworkInterface carrier */
    @Override
    public void setCarrierEnabled(@NonNull TestNetworkInterface iface, boolean enabled) {
        enforceTestNetworkPermissions(mContext);
        nativeSetTunTapCarrierEnabled(iface.getInterfaceName(), iface.getFileDescriptor().getFd(),
                enabled);
        // Explicitly close fd after use to prevent StrictMode from complaining.
        // Also, explicitly referencing iface guarantees that the object is not garbage collected
        // before nativeSetTunTapCarrierEnabled() executes.
        try {
            iface.getFileDescriptor().close();
        } catch (IOException e) {
            // if the close fails, there is not much that can be done -- move on.
        }
    }
}