Vendor AIDL interface for port blocking via eBPF

New Connectivity Service exposed to vendor for
restricting certain ports for use only in vendor.

Bug: 179733303

Change-Id: Iad9aff6924498ede5a08cfa5482082f094c0a90b
diff --git a/service/Android.bp b/service/Android.bp
index 0e6fe92..25b970a 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -19,6 +19,54 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+aidl_interface {
+    name: "connectivity_native_aidl_interface",
+    local_include_dir: "binder",
+    vendor_available: true,
+    srcs: [
+        "binder/android/net/connectivity/aidl/*.aidl",
+    ],
+    backend: {
+        java: {
+            apex_available: [
+                "com.android.tethering",
+            ],
+            min_sdk_version: "30",
+        },
+        ndk: {
+            apex_available: [
+                "com.android.tethering",
+            ],
+            min_sdk_version: "30",
+        },
+    },
+    versions: ["1"],
+
+}
+
+cc_library_static {
+    name: "connectivity_native_aidl_interface-lateststable-ndk",
+    min_sdk_version: "30",
+    whole_static_libs: [
+        "connectivity_native_aidl_interface-V1-ndk",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
+java_library {
+    name: "connectivity_native_aidl_interface-lateststable-java",
+    sdk_version: "system_current",
+    min_sdk_version: "30",
+    static_libs: [
+        "connectivity_native_aidl_interface-V1-java",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
 // The library name match the service-connectivity jarjar rules that put the JNI utils in the
 // android.net.connectivity.com.android.net.module.util package.
 cc_library_shared {
@@ -35,6 +83,7 @@
     ],
     static_libs: [
         "libnet_utils_device_common_bpfjni",
+        "libnet_utils_device_common_bpfutils",
     ],
     shared_libs: [
         "liblog",
@@ -109,6 +158,7 @@
     static_libs: [
         // Do not add libs here if they are already included
         // in framework-connectivity
+        "connectivity_native_aidl_interface-lateststable-java",
         "dnsresolver_aidl_interface-V9-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
diff --git a/service/aidl_api/connectivity_native_aidl_interface/1/.hash b/service/aidl_api/connectivity_native_aidl_interface/1/.hash
new file mode 100644
index 0000000..4625b4b
--- /dev/null
+++ b/service/aidl_api/connectivity_native_aidl_interface/1/.hash
@@ -0,0 +1 @@
+037b467eb02b172a3161e11bbc3dd691aebb5fce
diff --git a/service/aidl_api/connectivity_native_aidl_interface/1/android/net/connectivity/aidl/ConnectivityNative.aidl b/service/aidl_api/connectivity_native_aidl_interface/1/android/net/connectivity/aidl/ConnectivityNative.aidl
new file mode 100644
index 0000000..b3985a4
--- /dev/null
+++ b/service/aidl_api/connectivity_native_aidl_interface/1/android/net/connectivity/aidl/ConnectivityNative.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2022, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.connectivity.aidl;
+interface ConnectivityNative {
+  void blockPortForBind(in int port);
+  void unblockPortForBind(in int port);
+  void unblockAllPortsForBind();
+  int[] getPortsBlockedForBind();
+}
diff --git a/service/aidl_api/connectivity_native_aidl_interface/current/android/net/connectivity/aidl/ConnectivityNative.aidl b/service/aidl_api/connectivity_native_aidl_interface/current/android/net/connectivity/aidl/ConnectivityNative.aidl
new file mode 100644
index 0000000..b3985a4
--- /dev/null
+++ b/service/aidl_api/connectivity_native_aidl_interface/current/android/net/connectivity/aidl/ConnectivityNative.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2022, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.connectivity.aidl;
+interface ConnectivityNative {
+  void blockPortForBind(in int port);
+  void unblockPortForBind(in int port);
+  void unblockAllPortsForBind();
+  int[] getPortsBlockedForBind();
+}
diff --git a/service/binder/android/net/connectivity/aidl/ConnectivityNative.aidl b/service/binder/android/net/connectivity/aidl/ConnectivityNative.aidl
new file mode 100644
index 0000000..31e24b4
--- /dev/null
+++ b/service/binder/android/net/connectivity/aidl/ConnectivityNative.aidl
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2022, 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 android.net.connectivity.aidl;
+
+interface ConnectivityNative {
+    /**
+     * Blocks a port from being assigned during bind(). The caller is responsible for updating
+     * /proc/sys/net/ipv4/ip_local_port_range with the port being blocked so that calls to connect()
+     * will not automatically assign one of the blocked ports.
+     * Will return success even if port was already blocked.
+     *
+     * @param port Int corresponding to port number.
+     *
+     * @throws IllegalArgumentException if the port is invalid.
+     * @throws SecurityException if the UID of the client doesn't have network stack permission.
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void blockPortForBind(in int port);
+
+    /**
+     * Unblocks a port that has previously been blocked.
+     * Will return success even if port was already unblocked.
+     *
+     * @param port Int corresponding to port number.
+     *
+     * @throws IllegalArgumentException if the port is invalid.
+     * @throws SecurityException if the UID of the client doesn't have network stack permission.
+     * @throws ServiceSpecificException in case of failure, with an error code corresponding to the
+     *         unix errno.
+     */
+    void unblockPortForBind(in int port);
+
+    /**
+     * Unblocks all ports that have previously been blocked.
+     */
+    void unblockAllPortsForBind();
+
+    /**
+     * Gets the list of ports that have been blocked.
+     *
+     * @return List of blocked ports.
+     */
+    int[] getPortsBlockedForBind();
+}
\ No newline at end of file
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 2f09e55..d91eb03 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -21,6 +21,7 @@
 
 int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
 int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_BpfUtils(JNIEnv* env, char const* class_name);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
@@ -35,6 +36,9 @@
     if (register_com_android_net_module_util_TcUtils(env,
             "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
 
+    if (register_com_android_net_module_util_BpfUtils(env,
+            "android/net/connectivity/com/android/net/module/util/BpfUtils") < 0) return JNI_ERR;
+
     return JNI_VERSION_1_6;
 }
 
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
new file mode 100644
index 0000000..cde6ea7
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 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.connectivity;
+
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.connectivity.aidl.ConnectivityNative;
+import android.os.Binder;
+import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BpfBitmap;
+import com.android.net.module.util.BpfUtils;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.PermissionUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class ConnectivityNativeService extends ConnectivityNative.Stub {
+    public static final String SERVICE_NAME = "connectivity_native";
+
+    private static final String TAG = ConnectivityNativeService.class.getSimpleName();
+    private static final String CGROUP_PATH = "/sys/fs/cgroup";
+    private static final String V4_PROG_PATH =
+            "/sys/fs/bpf/prog_block_bind4_block_port";
+    private static final String V6_PROG_PATH =
+            "/sys/fs/bpf/prog_block_bind6_block_port";
+    private static final String BLOCKED_PORTS_MAP_PATH = "/sys/fs/bpf/map_block_blocked_ports_map";
+
+    private final Context mContext;
+
+    // BPF map for port blocking. Exactly 65536 entries long, with one entry per port number
+    @Nullable
+    private final BpfBitmap mBpfBlockedPortsMap;
+
+    /**
+     * Dependencies of ConnectivityNativeService, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Get BPF maps. */
+        @Nullable public BpfBitmap getBlockPortsMap() {
+            try {
+                return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);
+            } catch (ErrnoException e) {
+                throw new UnsupportedOperationException("Failed to create blocked ports map: "
+                        + e);
+            }
+        }
+    }
+
+    private void enforceBlockPortPermission() {
+        final int uid = Binder.getCallingUid();
+        if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
+        PermissionUtils.enforceNetworkStackPermission(mContext);
+    }
+
+    private void ensureValidPortNumber(int port) {
+        if (port < 0 || port > 65535) {
+            throw new IllegalArgumentException("Invalid port number " + port);
+        }
+    }
+
+    public ConnectivityNativeService(final Context context) {
+        this(context, new Dependencies());
+    }
+
+    @VisibleForTesting
+    protected ConnectivityNativeService(final Context context, @NonNull Dependencies deps) {
+        mContext = context;
+        mBpfBlockedPortsMap = deps.getBlockPortsMap();
+        attachProgram();
+    }
+
+    @Override
+    public void blockPortForBind(int port) {
+        enforceBlockPortPermission();
+        ensureValidPortNumber(port);
+        try {
+            mBpfBlockedPortsMap.set(port);
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno, e.getMessage());
+        }
+    }
+
+    @Override
+    public void unblockPortForBind(int port) {
+        enforceBlockPortPermission();
+        ensureValidPortNumber(port);
+        try {
+            mBpfBlockedPortsMap.unset(port);
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    "Could not unset bitmap value for (port: " + port + "): " + e);
+        }
+    }
+
+    @Override
+    public void unblockAllPortsForBind() {
+        enforceBlockPortPermission();
+        try {
+            mBpfBlockedPortsMap.clear();
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno, "Could not clear map: " + e);
+        }
+    }
+
+    @Override
+    public int[] getPortsBlockedForBind() {
+        enforceBlockPortPermission();
+
+        ArrayList<Integer> portMap = new ArrayList<Integer>();
+        for (int i = 0; i <= 65535; i++) {
+            try {
+                if (mBpfBlockedPortsMap.get(i)) portMap.add(i);
+            } catch (ErrnoException e) {
+                Log.e(TAG, "Failed to get index " + i, e);
+            }
+        }
+        return CollectionUtils.toIntArray(portMap);
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return this.HASH;
+    }
+
+    /**
+     * Attach BPF program
+     */
+    private void attachProgram() {
+        try {
+            BpfUtils.attachProgram(BPF_CGROUP_INET4_BIND, V4_PROG_PATH, CGROUP_PATH, 0);
+        } catch (IOException e) {
+            throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET4_BIND: "
+                    + e);
+        }
+        try {
+            BpfUtils.attachProgram(BPF_CGROUP_INET6_BIND, V6_PROG_PATH, CGROUP_PATH, 0);
+        } catch (IOException e) {
+            throw new UnsupportedOperationException("Unable to attach to BPF_CGROUP_INET6_BIND: "
+                    + e);
+        }
+        Log.d(TAG, "Attached BPF_CGROUP_INET4_BIND and BPF_CGROUP_INET6_BIND programs");
+    }
+}