From 7d2d414fd8e998206b91c85bbf46c8efee2d48de Mon Sep 17 00:00:00 2001 From: Chenbo Feng Date: Mon, 20 Nov 2017 18:23:46 -0800 Subject: [PATCH 1/3] Add BPF support for NetworkStatsService Add the native implementation for NetworkStatsService to read the uidStatsMap and tagStatsMap to get the network traffic information. Currently the implementation only try to get the data from bpf for a reference but the final value returned is still from old xt_qtaguid module. So the result will not be effected. The bpf implementation is depending on libbpf inside netd. Bug: 30950746 Test: CtsNetTestCases -t android.net.cts.TrafficStatsTest Change-Id: Ia85e4d4d602f6bd536739d89085e6dc37ad82e15 --- .../server/net/NetworkStatsService.java | 16 +++-- ...android_server_net_NetworkStatsService.cpp | 58 ++++++++++++++----- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 78fd4b4923..bfc150e1a9 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -887,17 +887,21 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public long getUidStats(int uid, int type) { - return nativeGetUidStat(uid, type); + return nativeGetUidStat(uid, type, checkBpfStatsEnable()); } @Override public long getIfaceStats(String iface, int type) { - return nativeGetIfaceStat(iface, type); + return nativeGetIfaceStat(iface, type, checkBpfStatsEnable()); } @Override public long getTotalStats(int type) { - return nativeGetTotalStat(type); + return nativeGetTotalStat(type, checkBpfStatsEnable()); + } + + private boolean checkBpfStatsEnable() { + return new File("/sys/fs/bpf/traffic_uid_stats_map").exists(); } /** @@ -1668,7 +1672,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static int TYPE_TCP_RX_PACKETS; private static int TYPE_TCP_TX_PACKETS; - private static native long nativeGetTotalStat(int type); - private static native long nativeGetIfaceStat(String iface, int type); - private static native long nativeGetUidStat(int uid, int type); + private static native long nativeGetTotalStat(int type, boolean useBpfStats); + private static native long nativeGetIfaceStat(String iface, int type, boolean useBpfStats); + private static native long nativeGetUidStat(int uid, int type, boolean useBpfStats); } diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp index 8de24e5565..3302dea535 100644 --- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp @@ -29,6 +29,15 @@ #include #include +#include "android-base/unique_fd.h" +#include "bpf/BpfNetworkStats.h" +#include "bpf/BpfUtils.h" + +using android::bpf::Stats; +using android::bpf::hasBpfSupport; +using android::bpf::bpfGetUidStats; +using android::bpf::bpfGetIfaceStats; + namespace android { static const char* QTAGUID_IFACE_STATS = "/proc/net/xt_qtaguid/iface_stat_fmt"; @@ -46,15 +55,6 @@ enum StatsType { TCP_TX_PACKETS = 5 }; -struct Stats { - uint64_t rxBytes; - uint64_t rxPackets; - uint64_t txBytes; - uint64_t txPackets; - uint64_t tcpRxPackets; - uint64_t tcpTxPackets; -}; - static uint64_t getStatsType(struct Stats* stats, StatsType type) { switch (type) { case RX_BYTES: @@ -150,9 +150,18 @@ static int parseUidStats(const uint32_t uid, struct Stats* stats) { return 0; } -static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) { +static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type, jboolean useBpfStats) { struct Stats stats; memset(&stats, 0, sizeof(Stats)); + + if (useBpfStats) { + if (bpfGetIfaceStats(NULL, &stats) == 0) { + return getStatsType(&stats, (StatsType) type); + } else { + return UNKNOWN; + } + } + if (parseIfaceStats(NULL, &stats) == 0) { return getStatsType(&stats, (StatsType) type); } else { @@ -160,7 +169,8 @@ static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) { } } -static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) { +static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type, + jboolean useBpfStats) { ScopedUtfChars iface8(env, iface); if (iface8.c_str() == NULL) { return UNKNOWN; @@ -168,6 +178,15 @@ static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) { struct Stats stats; memset(&stats, 0, sizeof(Stats)); + + if (useBpfStats) { + if (bpfGetIfaceStats(iface8.c_str(), &stats) == 0) { + return getStatsType(&stats, (StatsType) type); + } else { + return UNKNOWN; + } + } + if (parseIfaceStats(iface8.c_str(), &stats) == 0) { return getStatsType(&stats, (StatsType) type); } else { @@ -175,9 +194,18 @@ static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) { } } -static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) { +static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type, jboolean useBpfStats) { struct Stats stats; memset(&stats, 0, sizeof(Stats)); + + if (useBpfStats) { + if (bpfGetUidStats(uid, &stats) == 0) { + return getStatsType(&stats, (StatsType) type); + } else { + return UNKNOWN; + } + } + if (parseUidStats(uid, &stats) == 0) { return getStatsType(&stats, (StatsType) type); } else { @@ -186,9 +214,9 @@ static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) { } static const JNINativeMethod gMethods[] = { - {"nativeGetTotalStat", "(I)J", (void*) getTotalStat}, - {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat}, - {"nativeGetUidStat", "(II)J", (void*) getUidStat}, + {"nativeGetTotalStat", "(IZ)J", (void*) getTotalStat}, + {"nativeGetIfaceStat", "(Ljava/lang/String;IZ)J", (void*) getIfaceStat}, + {"nativeGetUidStat", "(IIZ)J", (void*) getUidStat}, }; int register_android_server_net_NetworkStatsService(JNIEnv* env) { From adfda6945d623b3fbbac10f5da7530904452853f Mon Sep 17 00:00:00 2001 From: Chenbo Feng Date: Mon, 20 Nov 2017 17:03:59 -0800 Subject: [PATCH 2/3] Add bpf support for NetworkStatsFactory Add the native method used to read the detail information of network stats from bpf maps. The native method of NetworkStatsFactory should choose the correct implementation to get the stats detail depending on the kernel version. Currently the bpf result is printed as a reference and the actual behavior of NetworkStatsFactory should not change. Test: NetworkStatsFactory related cts test should not fail. Bug: 30950746 Change-Id: I4715a23559b5b2306bd556cea0431f0ed172a993 --- .../internal/net/NetworkStatsFactory.java | 13 +-- ...droid_internal_net_NetworkStatsFactory.cpp | 85 +++++++++++-------- 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index b576a20db5..47666b57e8 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -62,6 +62,8 @@ public class NetworkStatsFactory { /** Path to {@code /proc/net/xt_qtaguid/stats}. */ private final File mStatsXtUid; + private boolean mUseBpfStats; + // TODO: to improve testability and avoid global state, do not use a static variable. @GuardedBy("sStackedIfaces") private static final ArrayMap sStackedIfaces = new ArrayMap<>(); @@ -77,14 +79,15 @@ public class NetworkStatsFactory { } public NetworkStatsFactory() { - this(new File("/proc/")); + this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists()); } @VisibleForTesting - public NetworkStatsFactory(File procRoot) { + public NetworkStatsFactory(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"); + mUseBpfStats = useBpfStats; } /** @@ -252,7 +255,7 @@ public class NetworkStatsFactory { stats = new NetworkStats(SystemClock.elapsedRealtime(), -1); } if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid, - limitIfaces, limitTag) != 0) { + limitIfaces, limitTag, mUseBpfStats) != 0) { throw new IOException("Failed to parse network stats"); } if (SANITY_CHECK_NATIVE) { @@ -346,6 +349,6 @@ public class NetworkStatsFactory { * are expected to monotonically increase since device boot. */ @VisibleForTesting - public static native int nativeReadNetworkStatsDetail( - NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag); + public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path, + int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats); } diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp index d254de65f7..99d9839570 100644 --- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp +++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp @@ -30,7 +30,14 @@ #include #include -#include + +#include "android-base/unique_fd.h" +#include "bpf/BpfNetworkStats.h" +#include "bpf/BpfUtils.h" + +using android::bpf::hasBpfSupport; +using android::bpf::parseBpfNetworkStatsDetail; +using android::bpf::stats_line; namespace android { @@ -53,17 +60,6 @@ static struct { jfieldID operations; } gNetworkStatsClassInfo; -struct stats_line { - char iface[32]; - int32_t uid; - int32_t set; - int32_t tag; - int64_t rxBytes; - int64_t rxPackets; - int64_t txBytes; - int64_t txPackets; -}; - static jobjectArray get_string_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow) { if (!grow) { @@ -97,33 +93,14 @@ static jlongArray get_long_array(JNIEnv* env, jobject obj, jfieldID field, int s return env->NewLongArray(size); } -static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, - jstring path, jint limitUid, jobjectArray limitIfacesObj, jint limitTag) { - ScopedUtfChars path8(env, path); - if (path8.c_str() == NULL) { - return -1; - } - - FILE *fp = fopen(path8.c_str(), "r"); +static int legacyReadNetworkStatsDetail(std::vector* lines, + const std::vector& limitIfaces, + int limitTag, int limitUid, const char* path) { + FILE* fp = fopen(path, "r"); if (fp == NULL) { return -1; } - Vector limitIfaces; - if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) { - int num = env->GetArrayLength(limitIfacesObj); - limitIfaces.setCapacity(num); - for (int i=0; iGetObjectArrayElement(limitIfacesObj, i); - ScopedUtfChars string8(env, string); - if (string8.c_str() != NULL) { - limitIfaces.add(String8(string8.c_str())); - } - } - } - - Vector lines; - int lastIdx = 1; int idx; char buffer[384]; @@ -215,7 +192,7 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, //ALOGI("skipping due to uid: %s", buffer); continue; } - lines.push_back(s); + lines->push_back(s); } else { //ALOGI("skipping due to bad remaining fields: %s", pos); } @@ -225,8 +202,42 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, ALOGE("Failed to close netstats file"); return -1; } + return 0; +} + +static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jstring path, + jint limitUid, jobjectArray limitIfacesObj, jint limitTag, + jboolean useBpfStats) { + ScopedUtfChars path8(env, path); + if (path8.c_str() == NULL) { + return -1; + } + + std::vector limitIfaces; + if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) { + int num = env->GetArrayLength(limitIfacesObj); + for (int i = 0; i < num; i++) { + jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i); + ScopedUtfChars string8(env, string); + if (string8.c_str() != NULL) { + limitIfaces.push_back(std::string(string8.c_str())); + } + } + } + std::vector lines; + + + if (useBpfStats) { + if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0) + return -1; + } else { + if (legacyReadNetworkStatsDetail(&lines, limitIfaces, limitTag, + limitUid, path8.c_str()) < 0) + return -1; + } int size = lines.size(); + bool grow = size > env->GetIntField(stats, gNetworkStatsClassInfo.capacity); ScopedLocalRef iface(env, get_string_array(env, stats, @@ -303,7 +314,7 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, static const JNINativeMethod gMethods[] = { { "nativeReadNetworkStatsDetail", - "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;I)I", + "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;IZ)I", (void*) readNetworkStatsDetail } }; From 75a1efb3f5d7dcc413dfb91011351ed85afe2cad Mon Sep 17 00:00:00 2001 From: Chenbo Feng Date: Thu, 18 Jan 2018 19:48:52 -0800 Subject: [PATCH 3/3] Use /proc/net/dev to get stats summary If the qtaguid proc file is no longer exist, the device is running new eBPF module to do traffic accounting. So the NetworkStatsFactory need to use the proc/net/dev interface to get the per interface traffic stats summary. Also, adding a test to verify the helper function work properly Bug: 30950746 Test: run NetworkStatsFactoryTest Change-Id: Ia36808bf02f1637dd41a3e7c50917b91b1a77524 --- .../internal/net/NetworkStatsFactory.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index 47666b57e8..902bd120e8 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -31,13 +31,17 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; +import com.google.android.collect.Lists; import libcore.io.IoUtils; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; import java.net.ProtocolException; +import java.util.ArrayList; import java.util.Objects; /** @@ -55,6 +59,8 @@ public class NetworkStatsFactory { // Used for correct stats accounting on clatd interfaces. private static final int IPV4V6_HEADER_DELTA = 20; + /** Path to {@code /proc/net/dev}. */ + private final File mStatsIfaceDev; /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */ private final File mStatsXtIfaceAll; /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */ @@ -84,12 +90,51 @@ public class NetworkStatsFactory { @VisibleForTesting public NetworkStatsFactory(File procRoot, boolean useBpfStats) { + mStatsIfaceDev = new File(procRoot, "net/dev"); 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"); mUseBpfStats = useBpfStats; } + @VisibleForTesting + public NetworkStats readNetworkStatsIfaceDev() throws IOException { + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); + + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(mStatsIfaceDev)); + + // skip first two header lines + reader.readLine(); + reader.readLine(); + + // parse remaining lines + String line; + while ((line = reader.readLine()) != null) { + String[] values = line.trim().split("\\:?\\s+"); + entry.iface = values[0]; + entry.uid = UID_ALL; + entry.set = SET_ALL; + entry.tag = TAG_NONE; + entry.rxBytes = Long.parseLong(values[1]); + entry.rxPackets = Long.parseLong(values[2]); + entry.txBytes = Long.parseLong(values[9]); + entry.txPackets = Long.parseLong(values[10]); + stats.addValues(entry); + } + } catch (NullPointerException|NumberFormatException e) { + throw new ProtocolException("problem parsing stats", e); + } finally { + IoUtils.closeQuietly(reader); + StrictMode.setThreadPolicy(savedPolicy); + } + return stats; + } + /** * Parse and return interface-level summary {@link NetworkStats} measured * using {@code /proc/net/dev} style hooks, which may include non IP layer @@ -99,6 +144,11 @@ public class NetworkStatsFactory { * @throws IllegalStateException when problem parsing stats. */ public NetworkStats readNetworkStatsSummaryDev() throws IOException { + + // Return the stats get from /proc/net/dev if switched to bpf module. + if (mUseBpfStats) + return readNetworkStatsIfaceDev(); + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); @@ -150,6 +200,11 @@ public class NetworkStatsFactory { * @throws IllegalStateException when problem parsing stats. */ public NetworkStats readNetworkStatsSummaryXt() throws IOException { + + // Return the stats get from /proc/net/dev if qtaguid module is replaced. + if (mUseBpfStats) + return readNetworkStatsIfaceDev(); + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); // return null when kernel doesn't support