diff --git a/Tethering/Android.bp b/Tethering/Android.bp index e4ce615321..a7ae21acbd 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -81,6 +81,7 @@ cc_library { "libnativehelper_compat_libc++", ], static_libs: [ + "libbpfmapjni", "libnetjniutils", ], diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java index 611c828036..7189933a38 100644 --- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java +++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java @@ -29,9 +29,9 @@ import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.net.module.util.BpfMap; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; -import com.android.networkstack.tethering.BpfMap; import com.android.networkstack.tethering.BpfUtils; import com.android.networkstack.tethering.Tether4Key; import com.android.networkstack.tethering.Tether4Value; diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp deleted file mode 100644 index 260dbc10b0..0000000000 --- a/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 -#include -#include -#include - -#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_networkstack_tethering_BpfMap_closeMap(JNIEnv *env, jobject clazz, - jint fd) { - int ret = close(fd); - - if (ret) jniThrowErrnoException(env, "closeMap", errno); - - return ret; -} - -static jint com_android_networkstack_tethering_BpfMap_bpfFdGet(JNIEnv *env, jobject clazz, - jstring path, jint mode) { - ScopedUtfChars pathname(env, path); - - jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast(mode)); - - if (fd < 0) jniThrowErrnoException(env, "bpfFdGet", errno); - - return fd; -} - -static void com_android_networkstack_tethering_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(fd), keyRO.get(), valueRO.get(), - static_cast(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_networkstack_tethering_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(fd), keyRO.get()); - - return throwIfNotEnoent(env, "deleteMapEntry", ret, errno); -} - -static jboolean com_android_networkstack_tethering_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(fd), nullptr, nextKeyRW.get()); - } else { - ScopedByteArrayRO keyRO(env, key); - ret = bpf::getNextMapKey(static_cast(fd), keyRO.get(), nextKeyRW.get()); - } - - return throwIfNotEnoent(env, "getNextMapKey", ret, errno); -} - -static jboolean com_android_networkstack_tethering_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(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_networkstack_tethering_BpfMap_closeMap }, - { "bpfFdGet", "(Ljava/lang/String;I)I", - (void*) com_android_networkstack_tethering_BpfMap_bpfFdGet }, - { "writeToMapEntry", "(I[B[BI)V", - (void*) com_android_networkstack_tethering_BpfMap_writeToMapEntry }, - { "deleteMapEntry", "(I[B)Z", - (void*) com_android_networkstack_tethering_BpfMap_deleteMapEntry }, - { "getNextMapKey", "(I[B[B)Z", - (void*) com_android_networkstack_tethering_BpfMap_getNextMapKey }, - { "findMapEntry", "(I[B[B)Z", - (void*) com_android_networkstack_tethering_BpfMap_findMapEntry }, - -}; - -int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env) { - return jniRegisterNativeMethods(env, - "com/android/networkstack/tethering/BpfMap", - gMethods, NELEM(gMethods)); -} - -}; // namespace android diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp index 02e602d99e..fbae615db7 100644 --- a/Tethering/jni/onload.cpp +++ b/Tethering/jni/onload.cpp @@ -23,7 +23,7 @@ namespace android { int register_android_net_util_TetheringUtils(JNIEnv* env); -int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env); +int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name); int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env); int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env); @@ -36,7 +36,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { if (register_android_net_util_TetheringUtils(env) < 0) return JNI_ERR; - if (register_com_android_networkstack_tethering_BpfMap(env) < 0) return JNI_ERR; + if (register_com_android_net_module_util_BpfMap(env, + "com/android/networkstack/tethering/util/BpfMap") < 0) return JNI_ERR; if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR; diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags index 75ecdce8d2..f62df7f5e3 100644 --- a/Tethering/proguard.flags +++ b/Tethering/proguard.flags @@ -4,7 +4,7 @@ static final int EVENT_*; } --keep class com.android.networkstack.tethering.BpfMap { +-keep class com.android.networkstack.tethering.util.BpfMap { native ; } diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 1df3e585f4..4a6b9aa87b 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -61,6 +61,7 @@ import androidx.annotation.Nullable; 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.BpfMap; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.Struct; diff --git a/Tethering/src/com/android/networkstack/tethering/BpfMap.java b/Tethering/src/com/android/networkstack/tethering/BpfMap.java deleted file mode 100644 index 1363dc5150..0000000000 --- a/Tethering/src/com/android/networkstack/tethering/BpfMap.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * 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.networkstack.tethering; - -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 com.android.internal.annotations.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 the key of the map. - * @param the value of the map. - */ -public class BpfMap 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 mKeyClass; - private final Class 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 key, - final Class 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 key, final Class 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 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/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java index f97270cde5..184d045904 100644 --- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java @@ -31,6 +31,7 @@ import android.system.ErrnoException; import android.system.OsConstants; import android.util.ArrayMap; +import com.android.net.module.util.BpfMap; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index ef4330a754..86ff7acb5b 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -99,10 +99,10 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.net.module.util.BpfMap; import com.android.net.module.util.NetworkStackConstants; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; -import com.android.networkstack.tethering.BpfMap; import com.android.networkstack.tethering.PrivateAddressCoordinator; import com.android.networkstack.tethering.Tether4Key; import com.android.networkstack.tethering.Tether4Value; diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java index acc042b147..c5969d236f 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -97,6 +97,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.net.module.util.BpfMap; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.Struct;