Merge "Log bpf rc file if it differs from the template files." into main
diff --git a/service/src/com/android/server/BpfLoaderRcUtils.java b/service/src/com/android/server/BpfLoaderRcUtils.java
new file mode 100644
index 0000000..1b6ee55
--- /dev/null
+++ b/service/src/com/android/server/BpfLoaderRcUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 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.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * BpfRcUtils is responsible for comparing the bpf loader rc file.
+ *
+ * {@hide}
+ */
+public class BpfLoaderRcUtils {
+    public static final String TAG = BpfLoaderRcUtils.class.getSimpleName();
+
+    private static final List<String> BPF_LOADER_RC_S_T = List.of(
+            "service bpfloader /system/bin/bpfloader",
+            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+            "rlimit memlock 1073741824 1073741824",
+            "oneshot",
+            "reboot_on_failure reboot,bpfloader-failed",
+            "updatable"
+    );
+
+    private static final List<String> BPF_LOADER_RC_U = List.of(
+            "service bpfloader /system/bin/bpfloader",
+            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+            "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+            "user root",
+            "rlimit memlock 1073741824 1073741824",
+            "oneshot",
+            "reboot_on_failure reboot,bpfloader-failed",
+            "updatable"
+    );
+
+    private static final List<String> BPF_LOADER_RC_UQPR2 = List.of(
+            "service bpfloader /system/bin/netbpfload",
+            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+            "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+            "user root",
+            "rlimit memlock 1073741824 1073741824",
+            "oneshot",
+            "reboot_on_failure reboot,bpfloader-failed",
+            "updatable"
+    );
+
+
+    private static final String BPF_LOADER_RC_FILE_PATH = "/etc/init/bpfloader.rc";
+    private static final String NET_BPF_LOAD_RC_FILE_PATH = "/etc/init/netbpfload.rc";
+
+    private BpfLoaderRcUtils() {
+    }
+
+    /**
+     * Load the bpf rc file content from the input stream.
+     */
+    @VisibleForTesting
+    public static List<String> loadExistingBpfRcFile(@NonNull InputStream inputStream) {
+        List<String> contents = new ArrayList<>();
+        boolean bpfSectionFound = false;
+        try (BufferedReader br = new BufferedReader(
+                new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                if (line.startsWith("#")) {
+                    continue;
+                }
+                // If bpf service section was found and new service or action section start. The
+                // read should stop.
+                if (bpfSectionFound && (line.startsWith("service ") || (line.startsWith("on ")))) {
+                    break;
+                }
+                if (line.startsWith("service bpfloader ")) {
+                    bpfSectionFound = true;
+                }
+                if (bpfSectionFound) {
+                    contents.add(line);
+                }
+            }
+        } catch (IOException e) {
+            Log.wtf("read input stream failed.", e);
+            contents.clear();
+            return contents;
+        }
+        return contents;
+    }
+
+    /**
+     * Check the bpfLoader rc file on the system image matches any of the template files.
+     */
+    public static boolean checkBpfLoaderRc() {
+        File bpfRcFile = new File(BPF_LOADER_RC_FILE_PATH);
+        if (!bpfRcFile.exists()) {
+            if (SdkLevel.isAtLeastU()) {
+                bpfRcFile = new File(NET_BPF_LOAD_RC_FILE_PATH);
+            }
+            if (!bpfRcFile.exists()) {
+                Log.wtf(TAG,
+                        "neither " + BPF_LOADER_RC_FILE_PATH + " nor " + NET_BPF_LOAD_RC_FILE_PATH
+                                + " exist.");
+                return false;
+            }
+            // Check bpf rc file in U QPR2
+            return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_UQPR2);
+        }
+
+        if (SdkLevel.isAtLeastU()) {
+            // Check bpf rc file in U
+            return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_U);
+        }
+        // Check bpf rc file in S/T
+        return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_S_T);
+    }
+
+    private static boolean compareBpfLoaderRc(@NonNull File bpfRcFile,
+            @NonNull List<String> template) {
+        try {
+            List<String> actualContent = loadExistingBpfRcFile(new FileInputStream(bpfRcFile));
+            if (!actualContent.equals(template)) {
+                Log.wtf(TAG, "BPF rc file is not same as the template files " + actualContent);
+                return false;
+            }
+        } catch (FileNotFoundException e) {
+            Log.wtf(bpfRcFile.getPath() + " doesn't exist.", e);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 3252acb..9268da5 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1876,6 +1876,10 @@
             activityManager.registerUidFrozenStateChangedCallback(
                     (Runnable r) -> r.run(), frozenStateChangedCallback);
         }
+
+        if (mDeps.isFeatureNotChickenedOut(mContext, LOG_BPF_RC)) {
+            mHandler.post(BpfLoaderRcUtils::checkBpfLoaderRc);
+        }
     }
 
     /**
@@ -3335,6 +3339,8 @@
     public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
             "allow_sysui_connectivity_reports";
 
+    public static final String LOG_BPF_RC = "log_bpf_rc_force_disable";
+
     private void enforceInternetPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.INTERNET,
diff --git a/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
new file mode 100644
index 0000000..2cf6b17
--- /dev/null
+++ b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.S)
+class BpfLoaderRcUtilsTest {
+    @Test
+    fun testLoadExistingBpfRcFile() {
+
+        val inputString = """
+            service a
+            # test comment
+            service bpfloader /system/bin/bpfloader
+                capabilities CHOWN SYS_ADMIN NET_ADMIN
+                group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+                user root
+                rlimit memlock 1073741824 1073741824
+                oneshot
+                # comment 漢字
+                reboot_on_failure reboot,bpfloader-failed
+                updatable
+            
+            #test comment
+            on b 
+              oneshot 
+              # test comment
+        """.trimIndent()
+        val expectedResult = listOf(
+            "service bpfloader /system/bin/bpfloader",
+            "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+            "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+            "user root",
+            "rlimit memlock 1073741824 1073741824",
+            "oneshot",
+            "reboot_on_failure reboot,bpfloader-failed",
+            "updatable"
+        )
+
+        assertEquals(expectedResult,
+                BpfLoaderRcUtils.loadExistingBpfRcFile(inputString.byteInputStream()))
+    }
+
+    @Test
+    fun testCheckBpfRcFile() {
+        assertTrue(BpfLoaderRcUtils.checkBpfLoaderRc())
+    }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 9fad766..8f5fd7c 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -161,6 +161,7 @@
 import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
 import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
 import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
+import static com.android.server.ConnectivityService.LOG_BPF_RC;
 import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
@@ -2157,6 +2158,8 @@
             switch (name) {
                 case ALLOW_SYSUI_CONNECTIVITY_REPORTS:
                     return true;
+                case LOG_BPF_RC:
+                    return true;
                 default:
                     return super.isFeatureNotChickenedOut(context, name);
             }
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index f4c62c5..958c4f2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -132,6 +132,7 @@
         it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
         it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
         it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
+        it[ConnectivityService.LOG_BPF_RC] = true
     }
     fun enableFeature(f: String) = enabledFeatures.set(f, true)
     fun disableFeature(f: String) = enabledFeatures.set(f, false)