summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Wear <twear@quicinc.com>2021-10-19 11:01:46 -0700
committerTyler Wear <twear@quicinc.com>2021-10-29 12:38:05 -0700
commit4ab5d9fe561c2117c8e97de5b71595a280dd9ed0 (patch)
treea7d0d92bc779395db5098799fbca373a45a44308
parent9c4b67f3f5723c2087c9e054cc29b71b60945c06 (diff)
downloadnet-4ab5d9fe561c2117c8e97de5b71595a280dd9ed0.tar.gz
bpfmap: Move to Common Util Location
Multiple packages need access to bpf maps. Moving to common location to allow access from all necessary packages. Test: atest BpfMapTest Bug: 179733303 Change-Id: Idae7b620c15c781b2e7980c3a3157f396cfaf66e
-rw-r--r--common/Android.bp1
-rw-r--r--common/device/com/android/net/module/util/BpfMap.java288
-rw-r--r--common/native/bpf_syscall_wrappers/Android.bp1
-rw-r--r--common/native/bpfmapjni/Android.bp44
-rw-r--r--common/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp136
5 files changed, 470 insertions, 0 deletions
diff --git a/common/Android.bp b/common/Android.bp
index 6a56889c..f1e7c85e 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -100,6 +100,7 @@ java_library {
// an individual Struct library, and remove the net-utils-framework-common lib dependency.
// But there is no need doing this at the moment.
srcs: [
+ "device/com/android/net/module/util/BpfMap.java",
"device/com/android/net/module/util/HexDump.java",
"device/com/android/net/module/util/Ipv6Utils.java",
"device/com/android/net/module/util/Struct.java",
diff --git a/common/device/com/android/net/module/util/BpfMap.java b/common/device/com/android/net/module/util/BpfMap.java
new file mode 100644
index 00000000..aa741528
--- /dev/null
+++ b/common/device/com/android/net/module/util/BpfMap.java
@@ -0,0 +1,288 @@
+/*
+ * 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.net.module.util;
+
+import static android.system.OsConstants.EEXIST;
+import static android.system.OsConstants.ENOENT;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.Struct;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+/**
+ * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
+ * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by
+ * passing syscalls with map file descriptor.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+public class BpfMap<K extends Struct, V extends Struct> implements AutoCloseable {
+ static {
+ System.loadLibrary("tetherutilsjni");
+ }
+
+ // Following definitions from kernel include/uapi/linux/bpf.h
+ public static final int BPF_F_RDWR = 0;
+ public static final int BPF_F_RDONLY = 1 << 3;
+ public static final int BPF_F_WRONLY = 1 << 4;
+
+ public static final int BPF_MAP_TYPE_HASH = 1;
+
+ private static final int BPF_F_NO_PREALLOC = 1;
+
+ private static final int BPF_ANY = 0;
+ private static final int BPF_NOEXIST = 1;
+ private static final int BPF_EXIST = 2;
+
+ private final int mMapFd;
+ private final Class<K> mKeyClass;
+ private final Class<V> mValueClass;
+ private final int mKeySize;
+ private final int mValueSize;
+
+ /**
+ * Create a BpfMap map wrapper with "path" of filesystem.
+ *
+ * @param flag the access mode, one of BPF_F_RDWR, BPF_F_RDONLY, or BPF_F_WRONLY.
+ * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
+ * @throws NullPointerException if {@code path} is null.
+ */
+ public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ mMapFd = bpfFdGet(path, flag);
+
+ mKeyClass = key;
+ mValueClass = value;
+ mKeySize = Struct.getSize(key);
+ mValueSize = Struct.getSize(value);
+ }
+
+ /**
+ * Constructor for testing only.
+ * The derived class implements an internal mocked map. It need to implement all functions
+ * which are related with the native BPF map because the BPF map handler is not initialized.
+ * See BpfCoordinatorTest#TestBpfMap.
+ */
+ @VisibleForTesting
+ protected BpfMap(final Class<K> key, final Class<V> value) {
+ mMapFd = -1;
+ mKeyClass = key;
+ mValueClass = value;
+ mKeySize = Struct.getSize(key);
+ mValueSize = Struct.getSize(value);
+ }
+
+ /**
+ * Update an existing or create a new key -> value entry in an eBbpf map.
+ * (use insertOrReplaceEntry() if you need to know whether insert or replace happened)
+ */
+ public void updateEntry(K key, V value) throws ErrnoException {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_ANY);
+ }
+
+ /**
+ * If the key does not exist in the map, insert key -> value entry into eBpf map.
+ * Otherwise IllegalStateException will be thrown.
+ */
+ public void insertEntry(K key, V value)
+ throws ErrnoException, IllegalStateException {
+ try {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
+ } catch (ErrnoException e) {
+ if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists");
+
+ throw e;
+ }
+ }
+
+ /**
+ * If the key already exists in the map, replace its value. Otherwise NoSuchElementException
+ * will be thrown.
+ */
+ public void replaceEntry(K key, V value)
+ throws ErrnoException, NoSuchElementException {
+ try {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
+ } catch (ErrnoException e) {
+ if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found");
+
+ throw e;
+ }
+ }
+
+ /**
+ * Update an existing or create a new key -> value entry in an eBbpf map.
+ * Returns true if inserted, false if replaced.
+ * (use updateEntry() if you don't care whether insert or replace happened)
+ * Note: see inline comment below if running concurrently with delete operations.
+ */
+ public boolean insertOrReplaceEntry(K key, V value)
+ throws ErrnoException {
+ try {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
+ return true; /* insert succeeded */
+ } catch (ErrnoException e) {
+ if (e.errno != EEXIST) throw e;
+ }
+ try {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
+ return false; /* replace succeeded */
+ } catch (ErrnoException e) {
+ if (e.errno != ENOENT) throw e;
+ }
+ /* If we reach here somebody deleted after our insert attempt and before our replace:
+ * this implies a race happened. The kernel bpf delete interface only takes a key,
+ * and not the value, so we can safely pretend the replace actually succeeded and
+ * was immediately followed by the other thread's delete, since the delete cannot
+ * observe the potential change to the value.
+ */
+ return false; /* pretend replace succeeded */
+ }
+
+ /** Remove existing key from eBpf map. Return false if map was not modified. */
+ public boolean deleteEntry(K key) throws ErrnoException {
+ return deleteMapEntry(mMapFd, key.writeToBytes());
+ }
+
+ /** Returns {@code true} if this map contains no elements. */
+ public boolean isEmpty() throws ErrnoException {
+ return getFirstKey() == null;
+ }
+
+ private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
+ final byte[] rawKey = getNextRawKey(
+ key == null ? null : key.writeToBytes());
+ if (rawKey == null) return null;
+
+ final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
+ buffer.order(ByteOrder.nativeOrder());
+ return Struct.parse(mKeyClass, buffer);
+ }
+
+ /**
+ * Get the next key of the passed-in key. If the passed-in key is not found, return the first
+ * key. If the passed-in key is the last one, return null.
+ *
+ * TODO: consider allowing null passed-in key.
+ */
+ public K getNextKey(@NonNull K key) throws ErrnoException {
+ Objects.requireNonNull(key);
+ return getNextKeyInternal(key);
+ }
+
+ private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException {
+ byte[] nextKey = new byte[mKeySize];
+ if (getNextMapKey(mMapFd, key, nextKey)) return nextKey;
+
+ return null;
+ }
+
+ /** Get the first key of eBpf map. */
+ public K getFirstKey() throws ErrnoException {
+ return getNextKeyInternal(null);
+ }
+
+ /** Check whether a key exists in the map. */
+ public boolean containsKey(@NonNull K key) throws ErrnoException {
+ Objects.requireNonNull(key);
+
+ final byte[] rawValue = getRawValue(key.writeToBytes());
+ return rawValue != null;
+ }
+
+ /** Retrieve a value from the map. Return null if there is no such key. */
+ public V getValue(@NonNull K key) throws ErrnoException {
+ Objects.requireNonNull(key);
+ final byte[] rawValue = getRawValue(key.writeToBytes());
+
+ if (rawValue == null) return null;
+
+ final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
+ buffer.order(ByteOrder.nativeOrder());
+ return Struct.parse(mValueClass, buffer);
+ }
+
+ private byte[] getRawValue(final byte[] key) throws ErrnoException {
+ byte[] value = new byte[mValueSize];
+ if (findMapEntry(mMapFd, key, value)) return value;
+
+ return null;
+ }
+
+ /**
+ * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
+ * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any
+ * other structural modifications to the map, such as adding entries or deleting other entries.
+ * Otherwise, iteration will result in undefined behaviour.
+ */
+ public void forEach(BiConsumer<K, V> action) throws ErrnoException {
+ @Nullable K nextKey = getFirstKey();
+
+ while (nextKey != null) {
+ @NonNull final K curKey = nextKey;
+ @NonNull final V value = getValue(curKey);
+
+ nextKey = getNextKey(curKey);
+ action.accept(curKey, value);
+ }
+ }
+
+ @Override
+ public void close() throws ErrnoException {
+ closeMap(mMapFd);
+ }
+
+ /**
+ * Clears the map. The map may already be empty.
+ *
+ * @throws ErrnoException if the map is already closed, if an error occurred during iteration,
+ * or if a non-ENOENT error occurred when deleting a key.
+ */
+ public void clear() throws ErrnoException {
+ K key = getFirstKey();
+ while (key != null) {
+ deleteEntry(key); // ignores ENOENT.
+ key = getFirstKey();
+ }
+ }
+
+ private static native int closeMap(int fd) throws ErrnoException;
+
+ private native int bpfFdGet(String path, int mode) throws ErrnoException, NullPointerException;
+
+ private native void writeToMapEntry(int fd, byte[] key, byte[] value, int flags)
+ throws ErrnoException;
+
+ private native boolean deleteMapEntry(int fd, byte[] key) throws ErrnoException;
+
+ // If key is found, the operation returns true and the nextKey would reference to the next
+ // element. If key is not found, the operation returns true and the nextKey would reference to
+ // the first element. If key is the last element, false is returned.
+ private native boolean getNextMapKey(int fd, byte[] key, byte[] nextKey) throws ErrnoException;
+
+ private native boolean findMapEntry(int fd, byte[] key, byte[] value) throws ErrnoException;
+}
diff --git a/common/native/bpf_syscall_wrappers/Android.bp b/common/native/bpf_syscall_wrappers/Android.bp
index 136342c7..1416b6bc 100644
--- a/common/native/bpf_syscall_wrappers/Android.bp
+++ b/common/native/bpf_syscall_wrappers/Android.bp
@@ -33,6 +33,7 @@ cc_library_headers {
"com.android.tethering",
],
visibility: [
+ "//frameworks/libs/net/common/native/bpfmapjni",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/Tethering",
"//system/bpf/libbpf_android",
diff --git a/common/native/bpfmapjni/Android.bp b/common/native/bpfmapjni/Android.bp
new file mode 100644
index 00000000..edbae7ce
--- /dev/null
+++ b/common/native/bpfmapjni/Android.bp
@@ -0,0 +1,44 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libbpfmapjni",
+ srcs: ["com_android_net_module_util_BpfMap.cpp"],
+ header_libs: [
+ "bpf_syscall_wrappers",
+ "jni_headers",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnativehelper_compat_libc++",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+ sdk_version: "30",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity/Tethering",
+ ],
+}
diff --git a/common/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/common/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
new file mode 100644
index 00000000..e25e17de
--- /dev/null
+++ b/common/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "nativehelper/scoped_primitive_array.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+
+static jint com_android_net_module_util_BpfMap_closeMap(JNIEnv *env, jobject clazz,
+ jint fd) {
+ int ret = close(fd);
+
+ if (ret) jniThrowErrnoException(env, "closeMap", errno);
+
+ return ret;
+}
+
+static jint com_android_net_module_util_BpfMap_bpfFdGet(JNIEnv *env, jobject clazz,
+ jstring path, jint mode) {
+ ScopedUtfChars pathname(env, path);
+
+ jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
+
+ if (fd < 0) jniThrowErrnoException(env, "bpfFdGet", errno);
+
+ return fd;
+}
+
+static void com_android_net_module_util_BpfMap_writeToMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray value, jint flags) {
+ ScopedByteArrayRO keyRO(env, key);
+ ScopedByteArrayRO valueRO(env, value);
+
+ int ret = bpf::writeToMapEntry(static_cast<int>(fd), keyRO.get(), valueRO.get(),
+ static_cast<int>(flags));
+
+ if (ret) jniThrowErrnoException(env, "writeToMapEntry", errno);
+}
+
+static jboolean throwIfNotEnoent(JNIEnv *env, const char* functionName, int ret, int err) {
+ if (ret == 0) return true;
+
+ if (err != ENOENT) jniThrowErrnoException(env, functionName, err);
+ return false;
+}
+
+static jboolean com_android_net_module_util_BpfMap_deleteMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key) {
+ ScopedByteArrayRO keyRO(env, key);
+
+ // On success, zero is returned. If the element is not found, -1 is returned and errno is set
+ // to ENOENT.
+ int ret = bpf::deleteMapEntry(static_cast<int>(fd), keyRO.get());
+
+ return throwIfNotEnoent(env, "deleteMapEntry", ret, errno);
+}
+
+static jboolean com_android_net_module_util_BpfMap_getNextMapKey(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray nextKey) {
+ // If key is found, the operation returns zero and sets the next key pointer to the key of the
+ // next element. If key is not found, the operation returns zero and sets the next key pointer
+ // to the key of the first element. If key is the last element, -1 is returned and errno is
+ // set to ENOENT. Other possible errno values are ENOMEM, EFAULT, EPERM, and EINVAL.
+ ScopedByteArrayRW nextKeyRW(env, nextKey);
+ int ret;
+ if (key == nullptr) {
+ // Called by getFirstKey. Find the first key in the map.
+ ret = bpf::getNextMapKey(static_cast<int>(fd), nullptr, nextKeyRW.get());
+ } else {
+ ScopedByteArrayRO keyRO(env, key);
+ ret = bpf::getNextMapKey(static_cast<int>(fd), keyRO.get(), nextKeyRW.get());
+ }
+
+ return throwIfNotEnoent(env, "getNextMapKey", ret, errno);
+}
+
+static jboolean com_android_net_module_util_BpfMap_findMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray value) {
+ ScopedByteArrayRO keyRO(env, key);
+ ScopedByteArrayRW valueRW(env, value);
+
+ // If an element is found, the operation returns zero and stores the element's value into
+ // "value". If no element is found, the operation returns -1 and sets errno to ENOENT.
+ int ret = bpf::findMapEntry(static_cast<int>(fd), keyRO.get(), valueRW.get());
+
+ return throwIfNotEnoent(env, "findMapEntry", ret, errno);
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "closeMap", "(I)I",
+ (void*) com_android_net_module_util_BpfMap_closeMap },
+ { "bpfFdGet", "(Ljava/lang/String;I)I",
+ (void*) com_android_net_module_util_BpfMap_bpfFdGet },
+ { "writeToMapEntry", "(I[B[BI)V",
+ (void*) com_android_net_module_util_BpfMap_writeToMapEntry },
+ { "deleteMapEntry", "(I[B)Z",
+ (void*) com_android_net_module_util_BpfMap_deleteMapEntry },
+ { "getNextMapKey", "(I[B[B)Z",
+ (void*) com_android_net_module_util_BpfMap_getNextMapKey },
+ { "findMapEntry", "(I[B[B)Z",
+ (void*) com_android_net_module_util_BpfMap_findMapEntry },
+
+};
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name) {
+ return jniRegisterNativeMethods(env,
+ class_name,
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android