Merge "[MS68.1] Register usage callback from NetworkPolicyManagerService"
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
index d3d8bba..223bdcd 100644
--- a/framework-t/Sources.bp
+++ b/framework-t/Sources.bp
@@ -129,6 +129,11 @@
         "src/android/net/EthernetNetworkSpecifier.java",
         "src/android/net/IEthernetManager.aidl",
         "src/android/net/IEthernetServiceListener.aidl",
+        "src/android/net/IInternalNetworkManagementListener.aidl",
+        "src/android/net/InternalNetworkUpdateRequest.java",
+        "src/android/net/InternalNetworkUpdateRequest.aidl",
+        "src/android/net/InternalNetworkManagementException.java",
+        "src/android/net/InternalNetworkManagementException.aidl",
         "src/android/net/ITetheredInterfaceCallback.aidl",
     ],
     path: "src",
diff --git a/core/java/android/net/InternalNetworkManagementException.aidl b/framework-t/src/android/net/InternalNetworkManagementException.aidl
similarity index 100%
rename from core/java/android/net/InternalNetworkManagementException.aidl
rename to framework-t/src/android/net/InternalNetworkManagementException.aidl
diff --git a/core/java/android/net/InternalNetworkManagementException.java b/framework-t/src/android/net/InternalNetworkManagementException.java
similarity index 100%
rename from core/java/android/net/InternalNetworkManagementException.java
rename to framework-t/src/android/net/InternalNetworkManagementException.java
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.aidl b/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
similarity index 100%
rename from core/java/android/net/InternalNetworkUpdateRequest.aidl
rename to framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/framework-t/src/android/net/InternalNetworkUpdateRequest.java
similarity index 100%
rename from core/java/android/net/InternalNetworkUpdateRequest.java
rename to framework-t/src/android/net/InternalNetworkUpdateRequest.java
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index c803a72..77b7f16 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -214,6 +216,18 @@
     }
 
     /**
+     * Attach the socket tagger implementation to the current process, to
+     * get notified when a socket's {@link FileDescriptor} is assigned to
+     * a thread. See {@link SocketTagger#set(SocketTagger)}.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void attachSocketTagger() {
+        NetworkManagementSocketTagger.install();
+    }
+
+    /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * <p>
diff --git a/service-t/Sources.bp b/service-t/Sources.bp
index b261e16..24bc91d 100644
--- a/service-t/Sources.bp
+++ b/service-t/Sources.bp
@@ -26,6 +26,8 @@
     srcs: [
         "src/com/android/server/net/NetworkIdentity*.java",
         "src/com/android/server/net/NetworkStats*.java",
+        "src/com/android/server/net/BpfInterfaceMapUpdater.java",
+        "src/com/android/server/net/InterfaceMapValue.java",
     ],
     path: "src",
     visibility: [
@@ -66,6 +68,7 @@
 filegroup {
     name: "services.connectivity-ethernet-sources",
     srcs: [
+        "src/com/android/server/net/DelayedDiskWrite.java",
         "src/com/android/server/net/IpConfigStore.java",
     ],
     path: "src",
@@ -97,3 +100,28 @@
         "//packages/modules/Connectivity:__subpackages__",
     ],
 }
+
+cc_library_shared {
+    name: "libcom_android_net_module_util_jni",
+    min_sdk_version: "30",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+    srcs: [
+        "jni/onload.cpp",
+    ],
+    stl: "libc++_static",
+    static_libs: [
+        "libnet_utils_device_common_bpfjni",
+    ],
+    shared_libs: [
+        "liblog",
+        "libnativehelper",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+    ],
+}
diff --git a/service-t/jni/onload.cpp b/service-t/jni/onload.cpp
new file mode 100644
index 0000000..bca4697
--- /dev/null
+++ b/service-t/jni/onload.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <log/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv *env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (register_com_android_net_module_util_BpfMap(env,
+            "com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+
+    return JNI_VERSION_1_6;
+}
+
+};
+
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
new file mode 100644
index 0000000..25c88eb
--- /dev/null
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.android.server.net;
+
+import android.content.Context;
+import android.net.INetd;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+/**
+ * Monitor interface added (without removed) and right interface name and its index to bpf map.
+ */
+public class BpfInterfaceMapUpdater {
+    private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
+    // This is current path but may be changed soon.
+    private static final String IFACE_INDEX_NAME_MAP_PATH =
+            "/sys/fs/bpf/map_netd_iface_index_name_map";
+    private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
+    private final INetd mNetd;
+    private final Handler mHandler;
+    private final Dependencies mDeps;
+
+    public BpfInterfaceMapUpdater(Context ctx, Handler handler) {
+        this(ctx, handler, new Dependencies());
+    }
+
+    @VisibleForTesting
+    public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
+        mDeps = deps;
+        mBpfMap = deps.getInterfaceMap();
+        mNetd = deps.getINetd(ctx);
+        mHandler = handler;
+    }
+
+    /**
+     * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Create BpfMap for updating interface and index mapping. */
+        public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+            try {
+                return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
+                    U32.class, InterfaceMapValue.class);
+            } catch (ErrnoException e) {
+                Log.e(TAG, "Cannot create interface map: " + e);
+                return null;
+            }
+        }
+
+        /** Get InterfaceParams for giving interface name. */
+        public InterfaceParams getInterfaceParams(String ifaceName) {
+            return InterfaceParams.getByName(ifaceName);
+        }
+
+        /** Get INetd binder object. */
+        public INetd getINetd(Context ctx) {
+            return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE));
+        }
+    }
+
+    /**
+     * Start listening interface update event.
+     * Query current interface names before listening.
+     */
+    public void start() {
+        mHandler.post(() -> {
+            if (mBpfMap == null) {
+                Log.wtf(TAG, "Fail to start: Null bpf map");
+                return;
+            }
+
+            try {
+                // TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead.
+                mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver());
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e);
+            }
+
+            final String[] ifaces;
+            try {
+                // TODO: use a netlink dump to get the current interface list.
+                ifaces = mNetd.interfaceGetList();
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.wtf(TAG, "Unable to query interface names by netd, " + e);
+                return;
+            }
+
+            for (String ifaceName : ifaces) {
+                addInterface(ifaceName);
+            }
+        });
+    }
+
+    private void addInterface(String ifaceName) {
+        final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName);
+            return;
+        }
+
+        try {
+            mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName));
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
+        }
+    }
+
+    private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener {
+        @Override
+        public void onInterfaceAdded(String ifName) {
+            mHandler.post(() -> addInterface(ifName));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/DelayedDiskWrite.java b/service-t/src/com/android/server/net/DelayedDiskWrite.java
similarity index 82%
rename from services/core/java/com/android/server/net/DelayedDiskWrite.java
rename to service-t/src/com/android/server/net/DelayedDiskWrite.java
index 8f09eb7..35dc455 100644
--- a/services/core/java/com/android/server/net/DelayedDiskWrite.java
+++ b/service-t/src/com/android/server/net/DelayedDiskWrite.java
@@ -26,21 +26,37 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 
+/**
+ * This class provides APIs to do a delayed data write to a given {@link OutputStream}.
+ */
 public class DelayedDiskWrite {
+    private static final String TAG = "DelayedDiskWrite";
+
     private HandlerThread mDiskWriteHandlerThread;
     private Handler mDiskWriteHandler;
     /* Tracks multiple writes on the same thread */
     private int mWriteSequence = 0;
-    private final String TAG = "DelayedDiskWrite";
 
+    /**
+     * Used to do a delayed data write to a given {@link OutputStream}.
+     */
     public interface Writer {
-        public void onWriteCalled(DataOutputStream out) throws IOException;
+        /**
+         * write data to a given {@link OutputStream}.
+         */
+        void onWriteCalled(DataOutputStream out) throws IOException;
     }
 
+    /**
+     * Do a delayed data write to a given output stream opened from filePath.
+     */
     public void write(final String filePath, final Writer w) {
         write(filePath, w, true);
     }
 
+    /**
+     * Do a delayed data write to a given output stream opened from filePath.
+     */
     public void write(final String filePath, final Writer w, final boolean open) {
         if (TextUtils.isEmpty(filePath)) {
             throw new IllegalArgumentException("empty file path");
@@ -77,7 +93,7 @@
             if (out != null) {
                 try {
                     out.close();
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
 
             // Quit if no more writes sent
diff --git a/service-t/src/com/android/server/net/InterfaceMapValue.java b/service-t/src/com/android/server/net/InterfaceMapValue.java
new file mode 100644
index 0000000..061f323
--- /dev/null
+++ b/service-t/src/com/android/server/net/InterfaceMapValue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * The value of bpf interface index map which is used for NetworkStatsService.
+ */
+public class InterfaceMapValue extends Struct {
+    @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+    public final byte[] interfaceName;
+
+    public InterfaceMapValue(String iface) {
+        final byte[] ifaceArray = iface.getBytes();
+        interfaceName = new byte[16];
+        // All array bytes after the interface name, if any, must be 0.
+        System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length);
+    }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index bb123a3..17f3455 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -26,10 +26,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.INetd;
+import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.NetworkStats;
 import android.net.UnderlyingNetworkInfo;
-import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemClock;
 
@@ -70,7 +70,7 @@
 
     private final boolean mUseBpfStats;
 
-    private final INetd mNetd;
+    private final Context mContext;
 
     /**
      * Guards persistent data access in this class
@@ -158,12 +158,12 @@
         NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
     }
 
-    public NetworkStatsFactory(@NonNull INetd netd) {
-        this(new File("/proc/"), true, netd);
+    public NetworkStatsFactory(@NonNull Context ctx) {
+        this(ctx, new File("/proc/"), true);
     }
 
     @VisibleForTesting
-    public NetworkStatsFactory(File procRoot, boolean useBpfStats, @NonNull INetd netd) {
+    public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
@@ -172,7 +172,7 @@
             mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
             mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
         }
-        mNetd = netd;
+        mContext = ctx;
     }
 
     public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -295,11 +295,12 @@
     }
 
     @GuardedBy("mPersistentDataLock")
-    private void requestSwapActiveStatsMapLocked() throws RemoteException {
-        // Ask netd to do a active map stats swap. When the binder call successfully returns,
+    private void requestSwapActiveStatsMapLocked() {
+        // Do a active map stats swap. When the binder call successfully returns,
         // the system server should be able to safely read and clean the inactive map
         // without race problem.
-        mNetd.trafficSwapActiveStatsMap();
+        final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        cm.swapActiveStatsMap();
     }
 
     /**
@@ -327,7 +328,7 @@
                 if (mUseBpfStats) {
                     try {
                         requestSwapActiveStatsMapLocked();
-                    } catch (RemoteException e) {
+                    } catch (RuntimeException e) {
                         throw new IOException(e);
                     }
                     // Stats are always read from the inactive map, so they must be read after the
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 1f87f7b..243d621 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -357,6 +357,9 @@
     @NonNull
     private final LocationPermissionChecker mLocationPermissionChecker;
 
+    @NonNull
+    private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+
     private static @NonNull File getDefaultSystemDir() {
         return new File(Environment.getDataDirectory(), "system");
     }
@@ -419,7 +422,7 @@
         final NetworkStatsService service = new NetworkStatsService(context,
                 INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
                 alarmManager, wakeLock, getDefaultClock(),
-                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(netd),
+                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
                 new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
                 new Dependencies());
 
@@ -454,6 +457,8 @@
         mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
                 mNetworkStatsSubscriptionsMonitor);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+        mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
+        mInterfaceMapUpdater.start();
     }
 
     /**
@@ -508,6 +513,13 @@
         public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
             return new LocationPermissionChecker(context);
         }
+
+        /** Create BpfInterfaceMapUpdater to update bpf interface map. */
+        @NonNull
+        public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+                @NonNull Context ctx, @NonNull Handler handler) {
+            return new BpfInterfaceMapUpdater(ctx, handler);
+        }
     }
 
     /**
@@ -988,8 +1000,17 @@
         }
 
         // TODO: switch to data layer stats once kernel exports
-        // for now, read network layer stats and flatten across all ifaces
-        final NetworkStats networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+        // for now, read network layer stats and flatten across all ifaces.
+        // This function is used to query NeworkStats for calle's uid. The only caller method
+        // TrafficStats#getDataLayerSnapshotForUid alrady claim no special permission to query
+        // its own NetworkStats.
+        final long ident = Binder.clearCallingIdentity();
+        final NetworkStats networkLayer;
+        try {
+            networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
 
         // splice in operation counts
         networkLayer.spliceOperationsFrom(mUidOperations);