Merge changes from topic "dump_map_status"
* changes:
Move Interface index name map dump to NetworkStatsService
Move stats map A/B dump to NetworkStatsService
Add BpfInterfaceMapUpdater#getIfNameByIndex
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index 3b44d81..57466a6 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -22,10 +22,12 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
@@ -136,4 +138,37 @@
mHandler.post(() -> addInterface(ifName));
}
}
+
+ /** get interface name by interface index from bpf map */
+ public String getIfNameByIndex(final long index) {
+ try {
+ final InterfaceMapValue value = mBpfMap.getValue(new U32(index));
+ if (value == null) {
+ Log.e(TAG, "No if name entry for index " + index);
+ return null;
+ }
+ return value.getInterfaceNameString();
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get entry for index " + index + ": " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Dump BPF map
+ *
+ * @param pw print writer
+ */
+ public void dump(final IndentingPrintWriter pw) {
+ pw.println("BPF map status:");
+ pw.increaseIndent();
+ BpfDump.dumpMapStatus(mBpfMap, pw, "IfaceIndexNameMap", IFACE_INDEX_NAME_MAP_PATH);
+ pw.decreaseIndent();
+ pw.println("BPF map content:");
+ pw.increaseIndent();
+ BpfDump.dumpMap(mBpfMap, pw, "IfaceIndexNameMap",
+ (key, value) -> "ifaceIndex=" + key.val
+ + " ifaceName=" + value.getInterfaceNameString());
+ pw.decreaseIndent();
+ }
}
diff --git a/service-t/src/com/android/server/net/InterfaceMapValue.java b/service-t/src/com/android/server/net/InterfaceMapValue.java
index 42c0044..95da981 100644
--- a/service-t/src/com/android/server/net/InterfaceMapValue.java
+++ b/service-t/src/com/android/server/net/InterfaceMapValue.java
@@ -16,20 +16,45 @@
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;
+
+import java.util.Arrays;
/**
* The value of bpf interface index map which is used for NetworkStatsService.
*/
public class InterfaceMapValue extends Struct {
+ private static final int IF_NAME_SIZE = 16;
+
@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);
+ interfaceName = Arrays.copyOf(iface.getBytes(), IF_NAME_SIZE);
+ }
+
+ /**
+ * Constructor for Struct#parse. Build this struct from byte array of interface name.
+ *
+ * @param ifName Byte array of interface name, length is expected to be IF_NAME_SIZE(16).
+ * If longer or shorter, interface name will be truncated or padded with zeros.
+ * All array bytes after the interface name, if any, must be 0.
+ */
+ public InterfaceMapValue(final byte[] ifName) {
+ interfaceName = Arrays.copyOf(ifName, IF_NAME_SIZE);
+ }
+
+ /** Returns the length of the null-terminated string. */
+ private int strlen(byte[] str) {
+ for (int i = 0; i < str.length; ++i) {
+ if (str[i] == '\0') {
+ return i;
+ }
+ }
+ return str.length;
+ }
+
+ public String getInterfaceNameString() {
+ return new String(interfaceName, 0 /* offset */, strlen(interfaceName));
}
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 8d5c881..09eb54c 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -2741,6 +2741,12 @@
}
pw.println();
+ pw.println("InterfaceMapUpdater:");
+ pw.increaseIndent();
+ mInterfaceMapUpdater.dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
pw.println("BPF map status:");
pw.increaseIndent();
dumpMapStatus(pw);
@@ -2755,6 +2761,8 @@
dumpCookieTagMapLocked(pw);
dumpUidCounterSetMapLocked(pw);
dumpAppUidStatsMapLocked(pw);
+ dumpStatsMapLocked(mStatsMapA, pw, "mStatsMapA");
+ dumpStatsMapLocked(mStatsMapB, pw, "mStatsMapB");
pw.decreaseIndent();
}
}
@@ -2840,6 +2848,8 @@
pw.println("mUidCounterSetMap: "
+ getMapStatus(mUidCounterSetMap, UID_COUNTERSET_MAP_PATH));
pw.println("mAppUidStatsMap: " + getMapStatus(mAppUidStatsMap, APP_UID_STATS_MAP_PATH));
+ pw.println("mStatsMapA: " + getMapStatus(mStatsMapA, STATS_MAP_A_PATH));
+ pw.println("mStatsMapB: " + getMapStatus(mStatsMapB, STATS_MAP_B_PATH));
}
@GuardedBy("mStatsLock")
@@ -2876,6 +2886,30 @@
+ value.txPackets);
}
+ @GuardedBy("mStatsLock")
+ private void dumpStatsMapLocked(final IBpfMap<StatsMapKey, StatsMapValue> statsMap,
+ final IndentingPrintWriter pw, final String mapName) {
+ if (statsMap == null) {
+ return;
+ }
+
+ BpfDump.dumpMap(statsMap, pw, mapName,
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
+ (key, value) -> {
+ final long ifIndex = key.ifaceIndex;
+ final String ifName = mInterfaceMapUpdater.getIfNameByIndex(ifIndex);
+ return ifIndex + " "
+ + (ifName != null ? ifName : "unknown") + " "
+ + "0x" + Long.toHexString(key.tag) + " "
+ + key.uid + " "
+ + key.counterSet + " "
+ + value.rxBytes + " "
+ + value.rxPackets + " "
+ + value.txBytes + " "
+ + value.txPackets;
+ });
+ }
+
private NetworkStats readNetworkStatsSummaryDev() {
try {
return mStatsFactory.readNetworkStatsSummaryDev();
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 7fb1b34..49b23c9 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -647,48 +647,6 @@
dw.println("mCookieTagMap print end with error: %s", res.error().message().c_str());
}
-
- // Print uidStatsMap content.
- std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes"
- " rxPackets txBytes txPackets");
- dumpBpfMap("mStatsMapA", dw, statsHeader);
- const auto printStatsInfo = [&dw, this](const StatsKey& key, const StatsValue& value,
- const BpfMap<StatsKey, StatsValue>&) {
- uint32_t ifIndex = key.ifaceIndex;
- auto ifname = mIfaceIndexNameMap.readValue(ifIndex);
- if (!ifname.ok()) {
- ifname = IfaceValue{"unknown"};
- }
- dw.println("%u %s 0x%x %u %u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, ifIndex,
- ifname.value().name, key.tag, key.uid, key.counterSet, value.rxBytes,
- value.rxPackets, value.txBytes, value.txPackets);
- return base::Result<void>();
- };
- res = mStatsMapA.iterateWithValue(printStatsInfo);
- if (!res.ok()) {
- dw.println("mStatsMapA print end with error: %s", res.error().message().c_str());
- }
-
- // Print TagStatsMap content.
- dumpBpfMap("mStatsMapB", dw, statsHeader);
- res = mStatsMapB.iterateWithValue(printStatsInfo);
- if (!res.ok()) {
- dw.println("mStatsMapB print end with error: %s", res.error().message().c_str());
- }
-
- // Print ifaceIndexToNameMap content.
- dumpBpfMap("mIfaceIndexNameMap", dw, "");
- const auto printIfaceNameInfo = [&dw](const uint32_t& key, const IfaceValue& value,
- const BpfMap<uint32_t, IfaceValue>&) {
- const char* ifname = value.name;
- dw.println("ifaceIndex=%u ifaceName=%s", key, ifname);
- return base::Result<void>();
- };
- res = mIfaceIndexNameMap.iterateWithValue(printIfaceNameInfo);
- if (!res.ok()) {
- dw.println("mIfaceIndexNameMap print end with error: %s", res.error().message().c_str());
- }
-
// Print ifaceStatsMap content
std::string ifaceStatsHeader = StringPrintf("ifaceIndex ifaceName rxBytes rxPackets txBytes"
" txPackets");
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index d08ffee..8525738 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -60,7 +60,6 @@
constexpr uint32_t TEST_TAG = 42;
constexpr uint32_t TEST_COUNTERSET = 1;
constexpr int TEST_COOKIE = 1;
-constexpr char TEST_IFNAME[] = "test0";
constexpr int TEST_IFINDEX = 999;
constexpr int RXPACKETS = 1;
constexpr int RXBYTES = 100;
@@ -792,17 +791,7 @@
// 999 test0 0x2a 10086 1 100 1 0 0
std::vector<std::string> expectedLines = {
"mCookieTagMap:",
- fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID),
- "mStatsMapA",
- "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
- fmt::format("{} {} {:#x} {} {} {} {} {} {}",
- TEST_IFINDEX, TEST_IFNAME, TEST_TAG, TEST_UID, TEST_COUNTERSET, RXBYTES,
- RXPACKETS, TXBYTES, TXPACKETS)};
-
- populateFakeIfaceIndexName(TEST_IFNAME, TEST_IFINDEX);
- expectedLines.emplace_back("mIfaceIndexNameMap:");
- expectedLines.emplace_back(fmt::format("ifaceIndex={} ifaceName={}",
- TEST_IFINDEX, TEST_IFNAME));
+ fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID)};
ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
TrafficController::IptOpInsert)));
@@ -829,9 +818,6 @@
std::vector<std::string> expectedLines = {
fmt::format("mCookieTagMap {}", kErrIterate),
- fmt::format("mStatsMapA {}", kErrIterate),
- fmt::format("mStatsMapB {}", kErrIterate),
- fmt::format("mIfaceIndexNameMap {}", kErrIterate),
fmt::format("mIfaceStatsMap {}", kErrIterate),
fmt::format("mConfigurationMap {}", kErrReadRulesConfig),
fmt::format("mConfigurationMap {}", kErrReadStatsMapConfig),
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
index c6852d1..83e6b5f 100644
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -16,7 +16,14 @@
package com.android.server.net;
+import static android.system.OsConstants.EPERM;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -27,6 +34,8 @@
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
+import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
import androidx.test.filters.SmallTest;
@@ -36,6 +45,7 @@
import com.android.net.module.util.Struct.U32;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
import org.junit.Before;
import org.junit.Test;
@@ -44,6 +54,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -56,7 +69,8 @@
private final TestLooper mLooper = new TestLooper();
private BaseNetdUnsolicitedEventListener mListener;
private BpfInterfaceMapUpdater mUpdater;
- @Mock private IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ private IBpfMap<U32, InterfaceMapValue> mBpfMap =
+ spy(new TestBpfMap<>(U32.class, InterfaceMapValue.class));
@Mock private INetd mNetd;
@Mock private Context mContext;
@@ -118,4 +132,43 @@
mLooper.dispatchAll();
verifyNoMoreInteractions(mBpfMap);
}
+
+ @Test
+ public void testGetIfNameByIndex() throws Exception {
+ mBpfMap.updateEntry(new U32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ assertEquals(TEST_INTERFACE_NAME, mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ @Test
+ public void testGetIfNameByIndexNoEntry() {
+ assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ @Test
+ public void testGetIfNameByIndexException() throws Exception {
+ doThrow(new ErrnoException("", EPERM)).when(mBpfMap).getValue(new U32(TEST_INDEX));
+ assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ private void assertDumpContains(final String dump, final String message) {
+ assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
+ dump.contains(message));
+ }
+
+ private String getDump() {
+ final StringWriter sw = new StringWriter();
+ mUpdater.dump(new IndentingPrintWriter(new PrintWriter(sw), " "));
+ return sw.toString();
+ }
+
+ @Test
+ public void testDump() throws ErrnoException {
+ mBpfMap.updateEntry(new U32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ mBpfMap.updateEntry(new U32(TEST_INDEX2), new InterfaceMapValue(TEST_INTERFACE_NAME2));
+
+ final String dump = getDump();
+ assertDumpContains(dump, "IfaceIndexNameMap: OK");
+ assertDumpContains(dump, "ifaceIndex=1 ifaceName=test1");
+ assertDumpContains(dump, "ifaceIndex=2 ifaceName=test2");
+ }
}
diff --git a/tests/unit/java/com/android/server/net/InterfaceMapValueTest.java b/tests/unit/java/com/android/server/net/InterfaceMapValueTest.java
new file mode 100644
index 0000000..ee13d5f
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/InterfaceMapValueTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InterfaceMapValueTest {
+ private static final String IF_NAME = "wlan0";
+ private static final byte[] IF_NAME_BYTE = new byte[]{'w', 'l', 'a', 'n', '0'};
+ private static final byte[] IF_NAME_BYTE_WITH_PADDING =
+ new byte[]{'w', 'l', 'a', 'n', '0', 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0}; // IF_NAME_BYTE_WITH_PADDING.length = 16
+ private static final byte[] IF_NAME_BYTE_LONG =
+ new byte[]{'w', 'l', 'a', 'n', '0', 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0}; // IF_NAME_BYTE_LONG.length = 24
+
+ @Test
+ public void testInterfaceMapValueFromString() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testInterfaceMapValueFromByte() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE_WITH_PADDING);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testInterfaceMapValueFromByteShort() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testInterfaceMapValueFromByteLong() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE_LONG);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testGetInterfaceNameString() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE_WITH_PADDING);
+ assertEquals(IF_NAME, value.getInterfaceNameString());
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index e27e4e2..fdbccba 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -2528,4 +2528,28 @@
assertDumpContains(dump, "uid rxBytes rxPackets txBytes txPackets");
assertDumpContains(dump, "1002 10000 10 6000 6");
}
+
+ private void doTestDumpStatsMap(final String expectedIfaceName) throws ErrnoException {
+ initBpfMapsWithTagData(UID_BLUE);
+
+ final String dump = getDump();
+ assertDumpContains(dump, "mStatsMapA: OK");
+ assertDumpContains(dump, "mStatsMapB: OK");
+ assertDumpContains(dump,
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets");
+ assertDumpContains(dump, "10 " + expectedIfaceName + " 0x2 1002 0 5000 5 3000 3");
+ assertDumpContains(dump, "10 " + expectedIfaceName + " 0x1 1002 0 5000 5 3000 3");
+ }
+
+ @Test
+ public void testDumpStatsMap() throws ErrnoException {
+ doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doTestDumpStatsMap("wlan0");
+ }
+
+ @Test
+ public void testDumpStatsMapUnknownInterface() throws ErrnoException {
+ doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doTestDumpStatsMap("unknown");
+ }
}