diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..27357f88d612d1078010ebee00d8d2840dee1c34 --- /dev/null +++ b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#include <jni.h> +#include <nativehelper/JNIHelp.h> + +#include "bpf_tethering.h" + +namespace android { + +static jobjectArray getBpfCounterNames(JNIEnv *env) { + size_t size = BPF_TETHER_ERR__MAX; + jobjectArray ret = env->NewObjectArray(size, env->FindClass("java/lang/String"), nullptr); + for (int i = 0; i < size; i++) { + env->SetObjectArrayElement(ret, i, env->NewStringUTF(bpf_tether_errors[i])); + } + return ret; +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "getBpfCounterNames", "()[Ljava/lang/String;", (void*) getBpfCounterNames }, +}; + +int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env) { + return jniRegisterNativeMethods(env, + "com/android/networkstack/tethering/BpfCoordinator", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp index 3766de90762de5979b31cbbd679e839de4476b9e..e31da60ef5a17a1110bbe8ec1a51bae0a12b824c 100644 --- a/Tethering/jni/onload.cpp +++ b/Tethering/jni/onload.cpp @@ -24,6 +24,7 @@ namespace android { int register_android_net_util_TetheringUtils(JNIEnv* env); int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env); +int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env); extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; @@ -36,6 +37,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { if (register_com_android_networkstack_tethering_BpfMap(env) < 0) return JNI_ERR; + if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR; + return JNI_VERSION_1_6; } diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 4ba51308b84adbb4a47e5ce26df46339c99736d9..985328fe604be49a751d838661a05575e0db814c 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -59,6 +59,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.Struct; import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim; import java.net.Inet4Address; @@ -84,6 +85,13 @@ import java.util.Set; * @hide */ public class BpfCoordinator { + // Ensure the JNI code is loaded. In production this will already have been loaded by + // TetherService, but for tests it needs to be either loaded here or loaded by every test. + // TODO: is there a better way? + static { + System.loadLibrary("tetherutilsjni"); + } + static final boolean DOWNSTREAM = true; static final boolean UPSTREAM = false; @@ -97,6 +105,10 @@ public class BpfCoordinator { private static final String TETHER_UPSTREAM6_FS_PATH = makeMapPath(UPSTREAM, 6); private static final String TETHER_STATS_MAP_PATH = makeMapPath("stats"); private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit"); + private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error"); + + /** The names of all the BPF counters defined in bpf_tethering.h. */ + public static final String[] sBpfCounterNames = getBpfCounterNames(); private static String makeMapPath(String which) { return "/sys/fs/bpf/tethering/map_offload_tether_" + which + "_map"; @@ -717,6 +729,12 @@ public class BpfCoordinator { dumpIpv4ForwardingRules(pw); pw.decreaseIndent(); + pw.println(); + pw.println("Forwarding counters:"); + pw.increaseIndent(); + dumpCounters(pw); + pw.decreaseIndent(); + dumpDone.open(); }); if (!dumpDone.block(DUMP_TIMEOUT_MS)) { @@ -814,6 +832,36 @@ public class BpfCoordinator { pw.decreaseIndent(); } + /** + * Simple struct that only contains a u32. Must be public because Struct needs access to it. + * TODO: make this a public inner class of Struct so anyone can use it as, e.g., Struct.U32? + */ + public static class U32Struct extends Struct { + @Struct.Field(order = 0, type = Struct.Type.U32) + public long val; + } + + private void dumpCounters(@NonNull IndentingPrintWriter pw) { + try (BpfMap<U32Struct, U32Struct> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, + BpfMap.BPF_F_RDONLY, U32Struct.class, U32Struct.class)) { + + map.forEach((k, v) -> { + String counterName; + try { + counterName = sBpfCounterNames[(int) k.val]; + } catch (IndexOutOfBoundsException e) { + // Should never happen because this code gets the counter name from the same + // include file as the BPF program that increments the counter. + Log.wtf(TAG, "Unknown tethering counter type " + k.val); + counterName = Long.toString(k.val); + } + if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val)); + }); + } catch (ErrnoException e) { + pw.println("Error dumping counter map: " + e); + } + } + /** IPv6 forwarding rule class. */ public static class Ipv6ForwardingRule { public final int upstreamIfindex; @@ -1296,4 +1344,6 @@ public class BpfCoordinator { final SparseArray<String> getInterfaceNamesForTesting() { return mInterfaceNames; } + + private static native String[] getBpfCounterNames(); } diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp index 5e4fe524f269180506a32b00140055e7fc06e3b6..6c479a0971e95db919aa3e8c3314b2401bdb214c 100644 --- a/Tethering/tests/unit/Android.bp +++ b/Tethering/tests/unit/Android.bp @@ -69,6 +69,7 @@ java_defaults { // For mockito extended "libdexmakerjvmtiagent", "libstaticjvmtiagent", + "libtetherutilsjni", ], }