Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9890f4e authored by Ricky Wai's avatar Ricky Wai
Browse files

Add Network Watchlist adb test commands

- Add shell command to set temp watchlist config for (CTS/GTS) tests
- Device will no longer use temp watchlist config after reboot
- Temp watchlist config will be removed after reboot
- Temp watchlist config will be applied on testOnly app only

Bug: 63908748
Test: Able to boot
Change-Id: I7dcc6637676c1871ef8fdb637d3d98d0ce78ce36
parent 9712788a
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -23,8 +23,10 @@ import android.net.INetdEventCallback;
import android.net.metrics.IpConnectivityLog;
import android.os.Binder;
import android.os.Process;
import android.os.ResultReceiver;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
@@ -80,6 +82,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
                    return;
                }
                try {
                    mService.init();
                    mService.initIpConnectivityMetrics();
                    mService.startWatchlistLogging();
                } catch (RemoteException e) {
@@ -127,6 +130,10 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
        mIpConnectivityMetrics = ipConnectivityMetrics;
    }

    private void init() {
        mConfig.removeTestModeConfig();
    }

    private void initIpConnectivityMetrics() {
        mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
                ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
@@ -151,6 +158,22 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
        }
    };

    private boolean isCallerShell() {
        final int callingUid = Binder.getCallingUid();
        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
    }

    @Override
    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
        if (!isCallerShell()) {
            Slog.w(TAG, "Only shell is allowed to call network watchlist shell commands");
            return;
        }
        (new NetworkWatchlistShellCommand(mContext)).exec(this, in, out, err, args, callback,
                resultReceiver);
    }

    @VisibleForTesting
    protected boolean startWatchlistLoggingImpl() throws RemoteException {
        if (DEBUG) {
+94 −0
Original line number Diff line number Diff line
/*
 * 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.net.watchlist;

import android.content.Context;
import android.content.Intent;
import android.net.NetworkWatchlistManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ShellCommand;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

/**
 * Network watchlist shell commands class, to provide a way to set temporary watchlist config for
 * testing in shell, so CTS / GTS can use it to verify if watchlist feature is working properly.
 */
class NetworkWatchlistShellCommand extends ShellCommand {

    final NetworkWatchlistManager mNetworkWatchlistManager;

    NetworkWatchlistShellCommand(Context context) {
        mNetworkWatchlistManager = new NetworkWatchlistManager(context);
    }

    @Override
    public int onCommand(String cmd) {
        if (cmd == null) {
            return handleDefaultCommands(cmd);
        }

        final PrintWriter pw = getOutPrintWriter();
        try {
            switch(cmd) {
                case "set-test-config":
                    return runSetTestConfig();
                default:
                    return handleDefaultCommands(cmd);
            }
        } catch (RemoteException e) {
            pw.println("Remote exception: " + e);
        }
        return -1;
    }

    /**
     * Method to get fd from input xml path, and set it as temporary watchlist config.
     */
    private int runSetTestConfig() throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        try {
            final String configXmlPath = getNextArgRequired();
            final ParcelFileDescriptor pfd = openFileForSystem(configXmlPath, "r");
            if (pfd != null) {
                final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor());
                WatchlistConfig.getInstance().setTestMode(fileStream);
            }
            pw.println("Success!");
        } catch (RuntimeException | IOException ex) {
            pw.println("Error: " + ex.toString());
            return -1;
        }
        return 0;
    }

    @Override
    public void onHelp() {
        final PrintWriter pw = getOutPrintWriter();
        pw.println("Network watchlist manager commands:");
        pw.println("  help");
        pw.println("    Print this help text.");
        pw.println("");
        pw.println("  set-test-config your_watchlist_config.xml");
        pw.println();
        Intent.printIntentArgsHelp(pw , "");
    }
}
+36 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.net.watchlist;

import android.os.FileUtils;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
@@ -32,6 +33,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -50,6 +52,8 @@ class WatchlistConfig {
    // Watchlist config that pushed by ConfigUpdater.
    private static final String NETWORK_WATCHLIST_DB_PATH =
            "/data/misc/network_watchlist/network_watchlist.xml";
    private static final String NETWORK_WATCHLIST_DB_FOR_TEST_PATH =
            "/data/misc/network_watchlist/network_watchlist_for_test.xml";

    // Hash for null / unknown config, a 32 byte array filled with content 0x00
    private static final byte[] UNKNOWN_CONFIG_HASH = new byte[32];
@@ -80,7 +84,7 @@ class WatchlistConfig {
    private boolean mIsSecureConfig = true;

    private final static WatchlistConfig sInstance = new WatchlistConfig();
    private final File mXmlFile;
    private File mXmlFile;

    private volatile CrcShaDigests mDomainDigests;
    private volatile CrcShaDigests mIpDigests;
@@ -232,7 +236,38 @@ class WatchlistConfig {
        return UNKNOWN_CONFIG_HASH;
    }

    /**
     * This method will copy temporary test config and temporary override network watchlist config
     * in memory. When device is rebooted, temporary test config will be removed, and system will
     * use back the original watchlist config.
     * Also, as temporary network watchlist config is not secure, we will mark it as insecure
     * config and will be applied to testOnly applications only.
     */
    public void setTestMode(InputStream testConfigInputStream) throws IOException {
        Log.i(TAG, "Setting watchlist testing config");
        // Copy test config
        FileUtils.copyToFileOrThrow(testConfigInputStream,
                new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH));
        // Mark config as insecure, so it will be applied to testOnly applications only
        mIsSecureConfig = false;
        // Reload watchlist config using test config file
        mXmlFile = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH);
        reloadConfig();
    }

    public void removeTestModeConfig() {
        try {
            final File f = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH);
            if (f.exists()) {
                f.delete();
            }
        } catch (Exception e) {
            Log.e(TAG, "Unable to delete test config");
        }
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Watchlist config hash: " + HexDump.toHexString(getWatchlistConfigHash()));
        pw.println("Domain CRC32 digest list:");
        if (mDomainDigests != null) {
            mDomainDigests.crc32Digests.dump(fd, pw, args);
+27 −3
Original line number Diff line number Diff line
@@ -117,6 +117,25 @@ class WatchlistLoggingHandler extends Handler {
        }
    }

    /**
     * Return if a given package has testOnly is true.
     */
    private boolean isPackageTestOnly(int uid) {
        final ApplicationInfo ai;
        try {
            final String[] packageNames = mPm.getPackagesForUid(uid);
            if (packageNames == null || packageNames.length == 0) {
                Slog.e(TAG, "Couldn't find package: " + packageNames);
                return false;
            }
            ai = mPm.getApplicationInfo(packageNames[0],0);
        } catch (NameNotFoundException e) {
            // Should not happen.
            return false;
        }
        return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0;
    }

     /**
     * Report network watchlist records if we collected enough data.
     */
@@ -146,16 +165,21 @@ class WatchlistLoggingHandler extends Handler {
        }
        final String cncDomain = searchAllSubDomainsInWatchlist(hostname);
        if (cncDomain != null) {
            insertRecord(getDigestFromUid(uid), cncDomain, timestamp);
            insertRecord(uid, cncDomain, timestamp);
        } else {
            final String cncIp = searchIpInWatchlist(ipAddresses);
            if (cncIp != null) {
                insertRecord(getDigestFromUid(uid), cncIp, timestamp);
                insertRecord(uid, cncIp, timestamp);
            }
        }
    }

    private boolean insertRecord(byte[] digest, String cncHost, long timestamp) {
    private boolean insertRecord(int uid, String cncHost, long timestamp) {
        if (!mConfig.isConfigSecure() && !isPackageTestOnly(uid)) {
            // Skip package if config is not secure and package is not TestOnly app.
            return true;
        }
        final byte[] digest = getDigestFromUid(uid);
        final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
        tryAggregateRecords();
        return result;