Merge "Add test case to test normalize"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index e4ce615..a7ae21a 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -81,6 +81,7 @@
"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 611c828..7189933 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 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 260dbc1..0000000
--- 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 <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_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<unsigned>(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<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_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<int>(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<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_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<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_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 02e602d..fbae615 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 @@
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 75ecdce..f62df7f 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 <methods>;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 1df3e58..4a6b9aa 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -61,6 +61,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.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 1363dc5..0000000
--- 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 <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/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp
index e27c811..8e29ed3 100644
--- a/Tethering/tests/privileged/Android.bp
+++ b/Tethering/tests/privileged/Android.bp
@@ -26,6 +26,7 @@
"libtetherutilsjni",
],
jni_uses_sdk_apis: true,
+ jarjar_rules: ":TetheringTestsJarJarRules",
visibility: ["//visibility:private"],
}
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 f97270c..184d045 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.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 ef4330a..86ff7ac 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.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 acc042b..c5969d2 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.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;
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index e7a40e5..1a0de32 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -66,6 +66,8 @@
// Activate interface using an unconnected datagram socket.
base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
ifr.ifr_flags = IFF_UP;
+ // Mark TAP interfaces as supporting multicast
+ if (!isTun) ifr.ifr_flags |= IFF_MULTICAST;
if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) {
throwException(env, errno, "activating", ifr.ifr_name);
diff --git a/service/src/com/android/server/connectivity/TcpKeepaliveController.java b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
index c480594..acfbb3c 100644
--- a/service/src/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity;
import static android.net.SocketKeepalive.DATA_RECEIVED;
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
@@ -29,6 +30,8 @@
import static android.system.OsConstants.IP_TTL;
import static android.system.OsConstants.TIOCOUTQ;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+
import android.annotation.NonNull;
import android.net.InvalidPacketException;
import android.net.NetworkUtils;
@@ -36,7 +39,6 @@
import android.net.TcpKeepalivePacketData;
import android.net.TcpKeepalivePacketDataParcelable;
import android.net.TcpRepairWindow;
-import android.net.util.KeepalivePacketDataUtil;
import android.os.Handler;
import android.os.MessageQueue;
import android.os.Messenger;
@@ -46,12 +48,18 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.IpUtils;
import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
import java.io.FileDescriptor;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
/**
* Manage tcp socket which offloads tcp keepalive.
@@ -82,6 +90,8 @@
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private static final int TCP_HEADER_LENGTH = 20;
+
// Reference include/uapi/linux/tcp.h
private static final int TCP_REPAIR = 19;
private static final int TCP_REPAIR_QUEUE = 20;
@@ -112,12 +122,81 @@
throws InvalidPacketException, InvalidSocketException {
try {
final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd);
- return KeepalivePacketDataUtil.fromStableParcelable(tcpDetails);
+ // TODO: consider building a TcpKeepalivePacketData directly from switchToRepairMode
+ return fromStableParcelable(tcpDetails);
} catch (InvalidPacketException | InvalidSocketException e) {
switchOutOfRepairMode(fd);
throw e;
}
}
+
+ /**
+ * Factory method to create tcp keepalive packet structure.
+ */
+ @VisibleForTesting
+ public static TcpKeepalivePacketData fromStableParcelable(
+ TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException {
+ final byte[] packet;
+ try {
+ if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null)
+ && (tcpDetails.srcAddress.length == 4 /* V4 IP length */)
+ && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) {
+ packet = buildV4Packet(tcpDetails);
+ } else {
+ // TODO: support ipv6
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+ return new TcpKeepalivePacketData(
+ InetAddress.getByAddress(tcpDetails.srcAddress),
+ tcpDetails.srcPort,
+ InetAddress.getByAddress(tcpDetails.dstAddress),
+ tcpDetails.dstPort,
+ packet,
+ tcpDetails.seq, tcpDetails.ack, tcpDetails.rcvWnd, tcpDetails.rcvWndScale,
+ tcpDetails.tos, tcpDetails.ttl);
+ } catch (UnknownHostException e) {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+ }
+
+ /**
+ * Build ipv4 tcp keepalive packet, not including the link-layer header.
+ */
+ // TODO : if this code is ever moved to the network stack, factorize constants with the ones
+ // over there.
+ // TODO: consider using Ipv4Utils.buildTcpv4Packet() instead
+ private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) {
+ final int length = IPV4_HEADER_MIN_LEN + TCP_HEADER_LENGTH;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.put((byte) 0x45); // IP version and IHL
+ buf.put((byte) tcpDetails.tos); // TOS
+ buf.putShort((short) length);
+ buf.putInt(0x00004000); // ID, flags=DF, offset
+ buf.put((byte) tcpDetails.ttl); // TTL
+ buf.put((byte) IPPROTO_TCP);
+ final int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(tcpDetails.srcAddress);
+ buf.put(tcpDetails.dstAddress);
+ buf.putShort((short) tcpDetails.srcPort);
+ buf.putShort((short) tcpDetails.dstPort);
+ buf.putInt(tcpDetails.seq); // Sequence Number
+ buf.putInt(tcpDetails.ack); // ACK
+ buf.putShort((short) 0x5010); // TCP length=5, flags=ACK
+ buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size
+ final int tcpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // TCP checksum
+ // URG is not set therefore the urgent pointer is zero.
+ buf.putShort((short) 0); // Urgent pointer
+
+ buf.putShort(ipChecksumOffset, com.android.net.module.util.IpUtils.ipChecksum(buf, 0));
+ buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
+ buf, 0, IPV4_HEADER_MIN_LEN, TCP_HEADER_LENGTH));
+
+ return buf.array();
+ }
+
/**
* Switch the tcp socket to repair mode and query detail tcp information.
*
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 85942b0..1872327 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -60,6 +60,7 @@
// uncomment when b/13249961 is fixed
// sdk_version: "current",
platform_apis: true,
+ required: ["ConnectivityChecker"],
}
// Networking CTS tests for development and release. These tests always target the platform SDK
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 78a01e2..d761c27 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -26,6 +26,8 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="{MODULE}.apk" />
</target_preparer>
+ <target_preparer class="com.android.testutils.ConnectivityCheckTargetPreparer">
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.cts" />
<option name="runtime-hint" value="9m4s" />
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 7d9a24b..e17200e 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -16,15 +16,11 @@
package android.net.cts.util;
-import static android.Manifest.permission.ACCESS_WIFI_STATE;
-import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
-import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION;
import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
-import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -48,15 +44,12 @@
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
import android.net.TestNetworkManager;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.IBinder;
-import android.os.SystemClock;
import android.system.Os;
import android.system.OsConstants;
import android.text.TextUtils;
@@ -64,23 +57,17 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.net.module.util.ConnectivitySettingsUtils;
-
-import junit.framework.AssertionFailedError;
+import com.android.testutils.ConnectUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
public final class CtsNetUtils {
private static final String TAG = CtsNetUtils.class.getSimpleName();
@@ -89,13 +76,7 @@
private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 10_000;
private static final int CONNECTIVITY_CHANGE_TIMEOUT_SECS = 30;
- private static final int MAX_WIFI_CONNECT_RETRIES = 10;
- private static final int WIFI_CONNECT_INTERVAL_MS = 500;
- // Constants used by WifiManager.ActionListener#onFailure. Although onFailure is SystemApi,
- // the error code constants are not (they probably should be ?)
- private static final int WIFI_ERROR_IN_PROGRESS = 1;
- private static final int WIFI_ERROR_BUSY = 2;
private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
private static final String PRIVATE_DNS_MODE_STRICT = "hostname";
public static final int HTTP_PORT = 80;
@@ -237,10 +218,6 @@
* @return The network that was newly connected.
*/
private Network connectToWifi(boolean expectLegacyBroadcast) {
- final TestNetworkCallback callback = new TestNetworkCallback();
- mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
- Network wifiNetwork = null;
-
ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
IntentFilter filter = new IntentFilter();
@@ -248,159 +225,17 @@
mContext.registerReceiver(receiver, filter);
try {
- // Clear the wifi config blocklist (not the BSSID blocklist)
- clearWifiBlocklist();
- SystemUtil.runShellCommand("svc wifi enable");
- final WifiConfiguration config = getOrCreateWifiConfiguration();
- connectToWifiConfig(config);
-
- // Ensure we get an onAvailable callback and possibly a CONNECTIVITY_ACTION.
- wifiNetwork = callback.waitForAvailable();
- assertNotNull("onAvailable callback not received after connecting to " + config.SSID,
- wifiNetwork);
+ final Network network = new ConnectUtil(mContext).ensureWifiConnected();
if (expectLegacyBroadcast) {
- assertTrue("CONNECTIVITY_ACTION not received after connecting to " + config.SSID,
+ assertTrue("CONNECTIVITY_ACTION not received after connecting to " + network,
receiver.waitForState());
}
+ return network;
} catch (InterruptedException ex) {
- fail("connectToWifi was interrupted");
+ throw new AssertionError("connectToWifi was interrupted", ex);
} finally {
- mCm.unregisterNetworkCallback(callback);
mContext.unregisterReceiver(receiver);
}
-
- return wifiNetwork;
- }
-
- private void connectToWifiConfig(WifiConfiguration config) {
- for (int i = 0; i < MAX_WIFI_CONNECT_RETRIES; i++) {
- final Integer error = runAsShell(NETWORK_SETTINGS, () -> {
- final ConnectWifiListener listener = new ConnectWifiListener();
- mWifiManager.connect(config, listener);
- return listener.connectFuture.get(
- CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
- });
-
- if (error == null) return;
-
- // Only retry for IN_PROGRESS and BUSY
- if (error != WIFI_ERROR_IN_PROGRESS && error != WIFI_ERROR_BUSY) {
- fail("Failed to connect to " + config.SSID + ": " + error);
- }
-
- Log.w(TAG, "connect failed with " + error + "; waiting before retry");
- SystemClock.sleep(WIFI_CONNECT_INTERVAL_MS);
- }
-
- fail("Failed to connect to " + config.SSID
- + " after " + MAX_WIFI_CONNECT_RETRIES + "retries");
- }
-
- private static class ConnectWifiListener implements WifiManager.ActionListener {
- /**
- * Future completed when the connect process ends. Provides the error code or null if none.
- */
- final CompletableFuture<Integer> connectFuture = new CompletableFuture<>();
- @Override
- public void onSuccess() {
- connectFuture.complete(null);
- }
-
- @Override
- public void onFailure(int reason) {
- connectFuture.complete(reason);
- }
- }
-
- private WifiConfiguration getOrCreateWifiConfiguration() {
- final List<WifiConfiguration> configs = runAsShell(NETWORK_SETTINGS,
- mWifiManager::getConfiguredNetworks);
- // If no network is configured, add a config for virtual access points if applicable
- if (configs.size() == 0) {
- final List<ScanResult> scanResults = getWifiScanResults();
- final WifiConfiguration virtualConfig = maybeConfigureVirtualNetwork(scanResults);
- assertNotNull("The device has no configured wifi network", virtualConfig);
-
- return virtualConfig;
- }
- // No need to add a configuration: there is already one.
- if (configs.size() > 1) {
- // For convenience in case of local testing on devices with multiple saved configs,
- // prefer the first configuration that is in range.
- // In actual tests, there should only be one configuration, and it should be usable as
- // assumed by WifiManagerTest.testConnect.
- Log.w(TAG, "Multiple wifi configurations found: "
- + configs.stream().map(c -> c.SSID).collect(Collectors.joining(", ")));
- final List<ScanResult> scanResultsList = getWifiScanResults();
- Log.i(TAG, "Scan results: " + scanResultsList.stream().map(c ->
- c.SSID + " (" + c.level + ")").collect(Collectors.joining(", ")));
- final Set<String> scanResults = scanResultsList.stream().map(
- s -> "\"" + s.SSID + "\"").collect(Collectors.toSet());
-
- return configs.stream().filter(c -> scanResults.contains(c.SSID))
- .findFirst().orElse(configs.get(0));
- }
- return configs.get(0);
- }
-
- private List<ScanResult> getWifiScanResults() {
- final CompletableFuture<List<ScanResult>> scanResultsFuture = new CompletableFuture<>();
- runAsShell(NETWORK_SETTINGS, () -> {
- final BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- scanResultsFuture.complete(mWifiManager.getScanResults());
- }
- };
- mContext.registerReceiver(receiver, new IntentFilter(SCAN_RESULTS_AVAILABLE_ACTION));
- mWifiManager.startScan();
- });
-
- try {
- return scanResultsFuture.get(CONNECTIVITY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
- } catch (ExecutionException | InterruptedException | TimeoutException e) {
- throw new AssertionFailedError("Wifi scan results not received within timeout");
- }
- }
-
- /**
- * If a virtual wifi network is detected, add a configuration for that network.
- * TODO(b/158150376): have the test infrastructure add virtual wifi networks when appropriate.
- */
- private WifiConfiguration maybeConfigureVirtualNetwork(List<ScanResult> scanResults) {
- // Virtual wifi networks used on the emulator and cloud testing infrastructure
- final List<String> virtualSsids = Arrays.asList("VirtWifi", "AndroidWifi");
- Log.d(TAG, "Wifi scan results: " + scanResults);
- final ScanResult virtualScanResult = scanResults.stream().filter(
- s -> virtualSsids.contains(s.SSID)).findFirst().orElse(null);
-
- // Only add the virtual configuration if the virtual AP is detected in scans
- if (virtualScanResult == null) return null;
-
- final WifiConfiguration virtualConfig = new WifiConfiguration();
- // ASCII SSIDs need to be surrounded by double quotes
- virtualConfig.SSID = "\"" + virtualScanResult.SSID + "\"";
- virtualConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-
- runAsShell(NETWORK_SETTINGS, () -> {
- final int networkId = mWifiManager.addNetwork(virtualConfig);
- assertTrue(networkId >= 0);
- assertTrue(mWifiManager.enableNetwork(networkId, false /* attemptConnect */));
- });
- return virtualConfig;
- }
-
- /**
- * Re-enable wifi networks that were blocklisted, typically because no internet connection was
- * detected the last time they were connected. This is necessary to make sure wifi can reconnect
- * to them.
- */
- private void clearWifiBlocklist() {
- runAsShell(NETWORK_SETTINGS, ACCESS_WIFI_STATE, () -> {
- for (WifiConfiguration cfg : mWifiManager.getConfiguredNetworks()) {
- assertTrue(mWifiManager.enableNetwork(cfg.networkId, false /* attemptConnect */));
- }
- });
}
/**
diff --git a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
index 8498b6f..6afa4e9 100644
--- a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
+++ b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
@@ -27,6 +27,7 @@
import android.os.Build;
import android.util.Log;
+import com.android.server.connectivity.TcpKeepaliveController;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -81,7 +82,7 @@
testInfo.tos = tos;
testInfo.ttl = ttl;
try {
- resultData = KeepalivePacketDataUtil.fromStableParcelable(testInfo);
+ resultData = TcpKeepaliveController.fromStableParcelable(testInfo);
} catch (InvalidPacketException e) {
fail("InvalidPacketException: " + e);
}
@@ -155,7 +156,7 @@
testInfo.ttl = ttl;
TcpKeepalivePacketData testData = null;
TcpKeepalivePacketDataParcelable resultData = null;
- testData = KeepalivePacketDataUtil.fromStableParcelable(testInfo);
+ testData = TcpKeepaliveController.fromStableParcelable(testInfo);
resultData = KeepalivePacketDataUtil.toStableParcelable(testData);
assertArrayEquals(resultData.srcAddress, IPV4_KEEPALIVE_SRC_ADDR);
assertArrayEquals(resultData.dstAddress, IPV4_KEEPALIVE_DST_ADDR);
@@ -198,11 +199,11 @@
testParcel.ttl = ttl;
final KeepalivePacketData testData =
- KeepalivePacketDataUtil.fromStableParcelable(testParcel);
+ TcpKeepaliveController.fromStableParcelable(testParcel);
final TcpKeepalivePacketDataParcelable parsedParcelable =
KeepalivePacketDataUtil.parseTcpKeepalivePacketData(testData);
final TcpKeepalivePacketData roundTripData =
- KeepalivePacketDataUtil.fromStableParcelable(parsedParcelable);
+ TcpKeepaliveController.fromStableParcelable(parsedParcelable);
// Generated packet is the same, but rcvWnd / wndScale will differ if scale is non-zero
assertTrue(testData.getPacket().length > 0);
@@ -210,11 +211,11 @@
testParcel.rcvWndScale = 0;
final KeepalivePacketData noScaleTestData =
- KeepalivePacketDataUtil.fromStableParcelable(testParcel);
+ TcpKeepaliveController.fromStableParcelable(testParcel);
final TcpKeepalivePacketDataParcelable noScaleParsedParcelable =
KeepalivePacketDataUtil.parseTcpKeepalivePacketData(noScaleTestData);
final TcpKeepalivePacketData noScaleRoundTripData =
- KeepalivePacketDataUtil.fromStableParcelable(noScaleParsedParcelable);
+ TcpKeepaliveController.fromStableParcelable(noScaleParsedParcelable);
assertEquals(noScaleTestData, noScaleRoundTripData);
assertTrue(noScaleTestData.getPacket().length > 0);
assertArrayEquals(noScaleTestData.getPacket(), noScaleRoundTripData.getPacket());