Merge "Allows Test Data Elements with Filter"
diff --git a/Cronet/TEST_MAPPING b/Cronet/TEST_MAPPING
deleted file mode 100644
index 9bc5c00..0000000
--- a/Cronet/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsNetHttpTestCases"
- }
- ]
-}
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index 68e3cf1..2c28b8d 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -59,6 +59,7 @@
"ctstestrunner-axt",
"ctstestserver",
"junit",
+ "hamcrest-library",
],
libs: [
"android.test.runner",
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
new file mode 100644
index 0000000..b07367a
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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 android.net.http.cts;
+
+import static android.net.http.cts.util.TestUtilsKt.assertOKStatusCode;
+import static android.net.http.cts.util.TestUtilsKt.skipIfNoInternetConnection;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.net.http.HttpEngine;
+import android.net.http.UrlRequest;
+import android.net.http.UrlResponseInfo;
+import android.net.http.cts.util.TestUrlRequestCallback;
+import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class HttpEngineTest {
+ private static final String HOST = "source.android.com";
+ private static final String URL = "https://" + HOST;
+
+ private HttpEngine.Builder mEngineBuilder;
+ private TestUrlRequestCallback mCallback;
+ private HttpEngine mEngine;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ skipIfNoInternetConnection(context);
+ mEngineBuilder = new HttpEngine.Builder(context);
+ mCallback = new TestUrlRequestCallback();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mEngine != null) {
+ mEngine.shutdown();
+ }
+ }
+
+ @Test
+ public void testHttpEngine_Default() throws Exception {
+ mEngine = mEngineBuilder.build();
+ UrlRequest.Builder builder =
+ mEngine.newUrlRequestBuilder(URL, mCallback, mCallback.getExecutor());
+ builder.build().start();
+
+ mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+ UrlResponseInfo info = mCallback.mResponseInfo;
+ assertOKStatusCode(info);
+ assertEquals("h2", info.getNegotiatedProtocol());
+ }
+
+ @Test
+ public void testHttpEngine_DisableHttp2() throws Exception {
+ mEngine = mEngineBuilder.setEnableHttp2(false).build();
+ UrlRequest.Builder builder =
+ mEngine.newUrlRequestBuilder(URL, mCallback, mCallback.getExecutor());
+ builder.build().start();
+
+ mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+ UrlResponseInfo info = mCallback.mResponseInfo;
+ assertOKStatusCode(info);
+ assertEquals("http/1.1", info.getNegotiatedProtocol());
+ }
+
+ @Test
+ public void testHttpEngine_EnableQuic() throws Exception {
+ // The hint doesn't guarantee that QUIC will win the race, just that it will race TCP.
+ // If this ends up being flaky, consider sending multiple requests.
+ mEngine = mEngineBuilder.setEnableQuic(true).addQuicHint(HOST, 443, 443).build();
+ UrlRequest.Builder builder =
+ mEngine.newUrlRequestBuilder(URL, mCallback, mCallback.getExecutor());
+ builder.build().start();
+
+ mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+ UrlResponseInfo info = mCallback.mResponseInfo;
+ assertOKStatusCode(info);
+ assertEquals("h3", info.getNegotiatedProtocol());
+ }
+
+ @Test
+ public void testHttpEngine_GetDefaultUserAgent() throws Exception {
+ assertThat(mEngineBuilder.getDefaultUserAgent(), containsString("AndroidHttpClient"));
+ }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/CronetUrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
similarity index 61%
rename from Cronet/tests/cts/src/android/net/http/cts/CronetUrlRequestTest.java
rename to Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
index 598be0e..d7d3679 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/CronetUrlRequestTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -16,22 +16,22 @@
package android.net.http.cts;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static android.net.http.cts.util.TestUtilsKt.assertOKStatusCode;
+import static android.net.http.cts.util.TestUtilsKt.skipIfNoInternetConnection;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
import android.content.Context;
-import android.net.ConnectivityManager;
import android.net.http.HttpEngine;
import android.net.http.UrlRequest;
import android.net.http.UrlRequest.Status;
import android.net.http.UrlResponseInfo;
-import android.net.http.cts.util.CronetCtsTestServer;
+import android.net.http.cts.util.HttpCtsTestServer;
import android.net.http.cts.util.TestStatusListener;
import android.net.http.cts.util.TestUrlRequestCallback;
import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
-import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -41,40 +41,29 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
-public class CronetUrlRequestTest {
- private static final String TAG = CronetUrlRequestTest.class.getSimpleName();
-
- @NonNull private HttpEngine mHttpEngine;
- @NonNull private TestUrlRequestCallback mCallback;
- @NonNull private ConnectivityManager mCm;
- @NonNull private CronetCtsTestServer mTestServer;
+public class UrlRequestTest {
+ private TestUrlRequestCallback mCallback;
+ private HttpCtsTestServer mTestServer;
+ private HttpEngine mHttpEngine;
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
- mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ skipIfNoInternetConnection(context);
HttpEngine.Builder builder = new HttpEngine.Builder(context);
- builder.setEnableHttpCache(HttpEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024)
- .setEnableHttp2(true)
- // .setEnableBrotli(true)
- .setEnableQuic(true);
mHttpEngine = builder.build();
mCallback = new TestUrlRequestCallback();
- mTestServer = new CronetCtsTestServer(context);
+ mTestServer = new HttpCtsTestServer(context);
}
@After
public void tearDown() throws Exception {
- mHttpEngine.shutdown();
- mTestServer.shutdown();
- }
-
- private static void assertGreaterThan(String msg, int first, int second) {
- assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
- }
-
- private void assertHasTestableNetworks() {
- assertNotNull("This test requires a working Internet connection", mCm.getActiveNetwork());
+ if (mHttpEngine != null) {
+ mHttpEngine.shutdown();
+ }
+ if (mTestServer != null) {
+ mTestServer.shutdown();
+ }
}
private UrlRequest buildUrlRequest(String url) {
@@ -83,18 +72,14 @@
@Test
public void testUrlRequestGet_CompletesSuccessfully() throws Exception {
- assertHasTestableNetworks();
String url = mTestServer.getSuccessUrl();
UrlRequest request = buildUrlRequest(url);
request.start();
mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
UrlResponseInfo info = mCallback.mResponseInfo;
- assertEquals(
- "Unexpected http status code from " + url + ".", 200, info.getHttpStatusCode());
- assertGreaterThan(
- "Received byte from " + url + " is 0.", (int) info.getReceivedByteCount(), 0);
+ assertOKStatusCode(info);
+ assertThat("Received byte count must be > 0", info.getReceivedByteCount(), greaterThan(0L));
}
@Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/CronetCtsTestServer.kt b/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
similarity index 82%
rename from Cronet/tests/cts/src/android/net/http/cts/util/CronetCtsTestServer.kt
rename to Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
index 3ccb571..87d5108 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/util/CronetCtsTestServer.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
@@ -19,8 +19,8 @@
import android.content.Context
import android.webkit.cts.CtsTestServer
-/** Extends CtsTestServer to handle POST requests and other cronet specific test requests */
-class CronetCtsTestServer(context: Context) : CtsTestServer(context) {
+/** Extends CtsTestServer to handle POST requests and other test specific requests */
+class HttpCtsTestServer(context: Context) : CtsTestServer(context) {
val successUrl: String = getAssetUrl("html/hello_world.html")
}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUploadDataProvider.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestUploadDataProvider.java
new file mode 100644
index 0000000..d047828
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUploadDataProvider.java
@@ -0,0 +1,294 @@
+/*
+ * 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 android.net.http.cts.util;
+
+import android.net.http.UploadDataProvider;
+import android.net.http.UploadDataSink;
+import android.os.ConditionVariable;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** An UploadDataProvider implementation used in tests. */
+public class TestUploadDataProvider extends UploadDataProvider {
+ // Indicates whether all success callbacks are synchronous or asynchronous.
+ // Doesn't apply to errors.
+ public enum SuccessCallbackMode {
+ SYNC,
+ ASYNC
+ }
+
+ // Indicates whether failures should throw exceptions, invoke callbacks
+ // synchronously, or invoke callback asynchronously.
+ public enum FailMode {
+ NONE,
+ THROWN,
+ CALLBACK_SYNC,
+ CALLBACK_ASYNC
+ }
+
+ private final ArrayList<byte[]> mReads = new ArrayList<byte[]>();
+ private final SuccessCallbackMode mSuccessCallbackMode;
+ private final Executor mExecutor;
+
+ private boolean mChunked;
+
+ // Index of read to fail on.
+ private int mReadFailIndex = -1;
+ // Indicates how to fail on a read.
+ private FailMode mReadFailMode = FailMode.NONE;
+
+ private FailMode mRewindFailMode = FailMode.NONE;
+
+ private FailMode mLengthFailMode = FailMode.NONE;
+
+ private int mNumReadCalls;
+ private int mNumRewindCalls;
+
+ private int mNextRead;
+ private boolean mStarted;
+ private boolean mReadPending;
+ private boolean mRewindPending;
+ // Used to ensure there are no read/rewind requests after a failure.
+ private boolean mFailed;
+
+ private final AtomicBoolean mClosed = new AtomicBoolean(false);
+ private final ConditionVariable mAwaitingClose = new ConditionVariable(false);
+
+ public TestUploadDataProvider(
+ SuccessCallbackMode successCallbackMode, final Executor executor) {
+ mSuccessCallbackMode = successCallbackMode;
+ mExecutor = executor;
+ }
+
+ // Adds the result to be returned by a successful read request. The
+ // returned bytes must all fit within the read buffer provided by Cronet.
+ // After a rewind, if there is one, all reads will be repeated.
+ public void addRead(byte[] read) {
+ if (mStarted) {
+ throw new IllegalStateException("Adding bytes after read");
+ }
+ mReads.add(read);
+ }
+
+ public void setReadFailure(int readFailIndex, FailMode readFailMode) {
+ mReadFailIndex = readFailIndex;
+ mReadFailMode = readFailMode;
+ }
+
+ public void setLengthFailure() {
+ mLengthFailMode = FailMode.THROWN;
+ }
+
+ public void setRewindFailure(FailMode rewindFailMode) {
+ mRewindFailMode = rewindFailMode;
+ }
+
+ public void setChunked(boolean chunked) {
+ mChunked = chunked;
+ }
+
+ public int getNumReadCalls() {
+ return mNumReadCalls;
+ }
+
+ public int getNumRewindCalls() {
+ return mNumRewindCalls;
+ }
+
+ /** Returns the cumulative length of all data added by calls to addRead. */
+ @Override
+ public long getLength() throws IOException {
+ if (mClosed.get()) {
+ throw new ClosedChannelException();
+ }
+ if (mLengthFailMode == FailMode.THROWN) {
+ throw new IllegalStateException("Sync length failure");
+ }
+ return getUploadedLength();
+ }
+
+ public long getUploadedLength() {
+ if (mChunked) {
+ return -1;
+ }
+ long length = 0;
+ for (byte[] read : mReads) {
+ length += read.length;
+ }
+ return length;
+ }
+
+ @Override
+ public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer)
+ throws IOException {
+ int currentReadCall = mNumReadCalls;
+ ++mNumReadCalls;
+ if (mClosed.get()) {
+ throw new ClosedChannelException();
+ }
+ assertIdle();
+
+ if (maybeFailRead(currentReadCall, uploadDataSink)) {
+ mFailed = true;
+ return;
+ }
+
+ mReadPending = true;
+ mStarted = true;
+
+ final boolean finalChunk = (mChunked && mNextRead == mReads.size() - 1);
+ if (mNextRead < mReads.size()) {
+ if ((byteBuffer.limit() - byteBuffer.position()) < mReads.get(mNextRead).length) {
+ throw new IllegalStateException("Read buffer smaller than expected.");
+ }
+ byteBuffer.put(mReads.get(mNextRead));
+ ++mNextRead;
+ } else {
+ throw new IllegalStateException("Too many reads: " + mNextRead);
+ }
+
+ Runnable completeRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ mReadPending = false;
+ uploadDataSink.onReadSucceeded(finalChunk);
+ }
+ };
+ if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
+ completeRunnable.run();
+ } else {
+ mExecutor.execute(completeRunnable);
+ }
+ }
+
+ @Override
+ public void rewind(final UploadDataSink uploadDataSink) throws IOException {
+ ++mNumRewindCalls;
+ if (mClosed.get()) {
+ throw new ClosedChannelException();
+ }
+ assertIdle();
+
+ if (maybeFailRewind(uploadDataSink)) {
+ mFailed = true;
+ return;
+ }
+
+ if (mNextRead == 0) {
+ // Should never try and rewind when rewinding does nothing.
+ throw new IllegalStateException("Unexpected rewind when already at beginning");
+ }
+
+ mRewindPending = true;
+ mNextRead = 0;
+
+ Runnable completeRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ mRewindPending = false;
+ uploadDataSink.onRewindSucceeded();
+ }
+ };
+ if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
+ completeRunnable.run();
+ } else {
+ mExecutor.execute(completeRunnable);
+ }
+ }
+
+ private void assertIdle() {
+ if (mReadPending) {
+ throw new IllegalStateException("Unexpected operation during read");
+ }
+ if (mRewindPending) {
+ throw new IllegalStateException("Unexpected operation during rewind");
+ }
+ if (mFailed) {
+ throw new IllegalStateException("Unexpected operation after failure");
+ }
+ }
+
+ private boolean maybeFailRead(int readIndex, final UploadDataSink uploadDataSink) {
+ if (readIndex != mReadFailIndex) return false;
+
+ switch (mReadFailMode) {
+ case THROWN:
+ throw new IllegalStateException("Thrown read failure");
+ case CALLBACK_SYNC:
+ uploadDataSink.onReadError(new IllegalStateException("Sync read failure"));
+ return true;
+ case CALLBACK_ASYNC:
+ Runnable errorRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ uploadDataSink.onReadError(
+ new IllegalStateException("Async read failure"));
+ }
+ };
+ mExecutor.execute(errorRunnable);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean maybeFailRewind(final UploadDataSink uploadDataSink) {
+ switch (mRewindFailMode) {
+ case THROWN:
+ throw new IllegalStateException("Thrown rewind failure");
+ case CALLBACK_SYNC:
+ uploadDataSink.onRewindError(new IllegalStateException("Sync rewind failure"));
+ return true;
+ case CALLBACK_ASYNC:
+ Runnable errorRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ uploadDataSink.onRewindError(
+ new IllegalStateException("Async rewind failure"));
+ }
+ };
+ mExecutor.execute(errorRunnable);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!mClosed.compareAndSet(false, true)) {
+ throw new AssertionError("Closed twice");
+ }
+ mAwaitingClose.open();
+ }
+
+ public void assertClosed() {
+ mAwaitingClose.block(5000);
+ if (!mClosed.get()) {
+ throw new AssertionError("Was not closed");
+ }
+ }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
new file mode 100644
index 0000000..d30c059
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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 android.net.http.cts.util
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.http.UrlResponseInfo
+import org.junit.Assert.assertEquals
+import org.junit.Assume.assumeNotNull
+
+fun skipIfNoInternetConnection(context: Context) {
+ val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
+ assumeNotNull(
+ "This test requires a working Internet connection", connectivityManager.getActiveNetwork())
+}
+
+fun assertOKStatusCode(info: UrlResponseInfo) {
+ assertEquals("Status code must be 200 OK", 200, info.getHttpStatusCode())
+}
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index 172670e..6d17476 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -1,4 +1,3 @@
-chenbruce@google.com
chiachangwang@google.com
cken@google.com
huangaaron@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 700a085..a1e81c8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -64,6 +64,9 @@
"name": "connectivity_native_test"
},
{
+ "name": "CtsNetHttpTestCases"
+ },
+ {
"name": "libclat_test"
},
{
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index c39269e..b7ca3af 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -21,6 +21,18 @@
#include <stdbool.h>
#include <stdint.h>
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
+// Offsets from beginning of L4 (TCP/UDP) header
+#define TCP_OFFSET(field) offsetof(struct tcphdr, field)
+#define UDP_OFFSET(field) offsetof(struct udphdr, field)
+
+// Offsets from beginning of L3 (IPv4/IPv6) header
+#define IP4_OFFSET(field) offsetof(struct iphdr, field)
+#define IP6_OFFSET(field) offsetof(struct ipv6hdr, field)
+
// this returns 0 iff skb->sk is NULL
static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 1272f96..84da79d 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -46,8 +46,9 @@
static const bool INGRESS = false;
static const bool EGRESS = true;
-#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
-#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+// Used for 'bool enable_tracing'
+static const bool TRACE_ON = true;
+static const bool TRACE_OFF = false;
// offsetof(struct iphdr, ihl) -- but that's a bitfield
#define IPPROTO_IHL_OFF 0
@@ -99,6 +100,19 @@
/* never actually used from ebpf */
DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
+// A single-element configuration array, packet tracing is enabled when 'true'.
+DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
+ AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, /*ignore_on_eng*/false,
+ /*ignore_on_user*/true, /*ignore_on_userdebug*/false)
+
+// A ring buffer on which packet information is pushed. This map will only be loaded
+// on eng and userdebug devices. User devices won't load this to save memory.
+DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
+ AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, /*ignore_on_eng*/false,
+ /*ignore_on_user*/true, /*ignore_on_userdebug*/false);
+
// iptables xt_bpf programs need to be usable by both netd and netutils_wrappers
// selinux contexts, because even non-xt_bpf iptables mutations are implemented as
// a full table dump, followed by an update in userspace, and then a reload into the kernel,
@@ -226,12 +240,72 @@
: bpf_skb_load_bytes(skb, L3_off, to, len);
}
+static __always_inline inline void do_packet_tracing(
+ const struct __sk_buff* const skb, const bool egress, const uint32_t uid,
+ const uint32_t tag, const bool enable_tracing, const unsigned kver) {
+ if (!enable_tracing) return;
+ if (kver < KVER(5, 8, 0)) return;
+
+ uint32_t mapKey = 0;
+ bool* traceConfig = bpf_packet_trace_enabled_map_lookup_elem(&mapKey);
+ if (traceConfig == NULL) return;
+ if (*traceConfig == false) return;
+
+ PacketTrace* pkt = bpf_packet_trace_ringbuf_reserve();
+ if (pkt == NULL) return;
+
+ // Errors from bpf_skb_load_bytes_net are ignored to favor returning something
+ // over returning nothing. In the event of an error, the kernel will fill in
+ // zero for the destination memory. Do not change the default '= 0' below.
+
+ uint8_t proto = 0;
+ uint8_t L4_off = 0;
+ uint8_t ipVersion = 0;
+ if (skb->protocol == htons(ETH_P_IP)) {
+ (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(protocol), &proto, sizeof(proto), kver);
+ (void)bpf_skb_load_bytes_net(skb, IPPROTO_IHL_OFF, &L4_off, sizeof(L4_off), kver);
+ L4_off = (L4_off & 0x0F) * 4; // IHL calculation.
+ ipVersion = 4;
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
+ L4_off = sizeof(struct ipv6hdr);
+ ipVersion = 6;
+ }
+
+ uint8_t flags = 0;
+ __be16 sport = 0, dport = 0;
+ if (proto == IPPROTO_TCP && L4_off >= 20) {
+ (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG32_OFF + 1, &flags, sizeof(flags), kver);
+ (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(source), &sport, sizeof(sport), kver);
+ (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(dest), &dport, sizeof(dport), kver);
+ } else if (proto == IPPROTO_UDP && L4_off >= 20) {
+ (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(source), &sport, sizeof(sport), kver);
+ (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(dest), &dport, sizeof(dport), kver);
+ }
+
+ pkt->timestampNs = bpf_ktime_get_boot_ns();
+ pkt->ifindex = skb->ifindex;
+ pkt->length = skb->len;
+
+ pkt->uid = uid;
+ pkt->tag = tag;
+ pkt->sport = sport;
+ pkt->dport = dport;
+
+ pkt->egress = egress;
+ pkt->ipProto = proto;
+ pkt->tcpFlags = flags;
+ pkt->ipVersion = ipVersion;
+
+ bpf_packet_trace_ringbuf_submit(pkt);
+}
+
static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, const unsigned kver) {
uint32_t flag = 0;
if (skb->protocol == htons(ETH_P_IP)) {
uint8_t proto;
// no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
- (void)bpf_skb_load_bytes_net(skb, IP_PROTO_OFF, &proto, sizeof(proto), kver);
+ (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(protocol), &proto, sizeof(proto), kver);
if (proto == IPPROTO_ESP) return true;
if (proto != IPPROTO_TCP) return false; // handles read failure above
uint8_t ihl;
@@ -247,7 +321,7 @@
} else if (skb->protocol == htons(ETH_P_IPV6)) {
uint8_t proto;
// no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
- (void)bpf_skb_load_bytes_net(skb, IPV6_PROTO_OFF, &proto, sizeof(proto), kver);
+ (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
if (proto == IPPROTO_ESP) return true;
if (proto != IPPROTO_TCP) return false; // handles read failure above
// if the read below fails, we'll just assume no TCP flags are set, which is fine.
@@ -319,6 +393,7 @@
}
static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, bool egress,
+ const bool enable_tracing,
const unsigned kver) {
uint32_t sock_uid = bpf_get_socket_uid(skb);
uint64_t cookie = bpf_get_socket_cookie(skb);
@@ -378,34 +453,51 @@
key.tag = 0;
}
+ do_packet_tracing(skb, egress, uid, tag, enable_tracing, kver);
update_stats_with_config(skb, egress, &key, *selectedMap);
update_app_uid_stats_map(skb, egress, &uid);
asm("%0 &= 1" : "+r"(match));
return match;
}
+DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
+ bpf_cgroup_ingress_trace, KVER(5, 8, 0), KVER_INF,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
+ "fs_bpf_netd_readonly", "", false, true, false)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
+}
+
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_4_19, KVER(4, 19, 0), KVER_INF)
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, INGRESS, KVER(4, 19, 0));
+ return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER(4, 19, 0));
}
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_14", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_4_14, KVER_NONE, KVER(4, 19, 0))
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, INGRESS, KVER_NONE);
+ return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE);
+}
+
+DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
+ bpf_cgroup_egress_trace, KVER(5, 8, 0), KVER_INF,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
+ "fs_bpf_netd_readonly", "", false, true, false)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
}
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_4_19, KVER(4, 19, 0), KVER_INF)
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, EGRESS, KVER(4, 19, 0));
+ return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER(4, 19, 0));
}
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_14", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_4_14, KVER_NONE, KVER(4, 19, 0))
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, EGRESS, KVER_NONE);
+ return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_NONE);
}
// WARNING: Android T's non-updatable netd depends on the name of this program.
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index cc88680..be604f9 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -69,6 +69,24 @@
uint64_t tcpTxPackets;
} Stats;
+typedef struct {
+ uint64_t timestampNs;
+ uint32_t ifindex;
+ uint32_t length;
+
+ uint32_t uid;
+ uint32_t tag;
+
+ __be16 sport;
+ __be16 dport;
+
+ bool egress;
+ uint8_t ipProto;
+ uint8_t tcpFlags;
+ uint8_t ipVersion; // 4=IPv4, 6=IPv6, 0=unknown
+} PacketTrace;
+STRUCT_SIZE(PacketTrace, 8+4+4 + 4+4 + 2+2 + 1+1+1+1);
+
// Since we cannot garbage collect the stats map since device boot, we need to make these maps as
// large as possible. The maximum size of number of map entries we can have is depend on the rlimit
// of MEM_LOCK granted to netd. The memory space needed by each map can be calculated by the
@@ -87,7 +105,8 @@
// dozable_uid_map: key: 4 bytes, value: 1 bytes, cost: 145216 bytes = 145Kbytes
// standby_uid_map: key: 4 bytes, value: 1 bytes, cost: 145216 bytes = 145Kbytes
// powersave_uid_map: key: 4 bytes, value: 1 bytes, cost: 145216 bytes = 145Kbytes
-// total: 4930Kbytes
+// packet_trace_ringbuf:key: 0 bytes, value: 24 bytes, cost: 32768 bytes = 32Kbytes
+// total: 4962Kbytes
// It takes maximum 4.9MB kernel memory space if all maps are full, which requires any devices
// running this module to have a memlock rlimit to be larger then 5MB. In the old qtaguid module,
// we don't have a total limit for data entries but only have limitation of tags each uid can have.
@@ -102,6 +121,7 @@
static const int IFACE_STATS_MAP_SIZE = 1000;
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 4000;
+static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
#ifdef __cplusplus
@@ -145,6 +165,8 @@
#define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
+#define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf"
+#define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map"
#endif // __cplusplus
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 6e40524..4147c9a 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -105,6 +105,8 @@
mService = service;
}
+ // This can be null when NearbyDeviceParcelable field not set for Presence device
+ // or the scan type is not recognized.
@Nullable
private static NearbyDevice toClientNearbyDevice(
NearbyDeviceParcelable nearbyDeviceParcelable,
diff --git a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
index f136695..21b4d7c 100644
--- a/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/AbstractDiscoveryProvider.java
@@ -40,7 +40,6 @@
protected final DiscoveryProviderController mController;
protected final Executor mExecutor;
protected Listener mListener;
- protected List<ScanFilter> mScanFilters;
/** Interface for listening to discovery providers. */
public interface Listener {
@@ -77,6 +76,12 @@
protected void invalidateScanMode() {}
/**
+ * Callback invoked to inform the provider of new provider scan filters which replaces any prior
+ * provider filters. Always invoked on the provider executor.
+ */
+ protected void onSetScanFilters(List<ScanFilter> filters) {}
+
+ /**
* Retrieves the controller for this discovery provider. Should never be invoked by subclasses,
* as a discovery provider should not be controlling itself. Using this method from subclasses
* could also result in deadlock.
@@ -138,7 +143,7 @@
@Override
public void setProviderScanFilters(List<ScanFilter> filters) {
- mScanFilters = filters;
+ mExecutor.execute(() -> onSetScanFilters(filters));
}
}
}
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index d828ef9..55176ba 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -37,6 +37,7 @@
import android.os.ParcelUuid;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.nearby.common.bluetooth.fastpair.Constants;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.presence.ExtendedAdvertisement;
@@ -63,6 +64,12 @@
// Don't block the thread as it may be used by other services.
private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor();
private final Injector mInjector;
+ private final Object mLock = new Object();
+ // Null when the filters are never set
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ private List<android.nearby.ScanFilter> mScanFilters;
private android.bluetooth.le.ScanCallback mScanCallbackLegacy =
new android.bluetooth.le.ScanCallback() {
@Override
@@ -193,8 +200,10 @@
Log.v(TAG, "Ble scan stopped.");
bluetoothLeScanner.stopScan(mScanCallback);
bluetoothLeScanner.stopScan(mScanCallbackLegacy);
- if (mScanFilters != null) {
- mScanFilters.clear();
+ synchronized (mLock) {
+ if (mScanFilters != null) {
+ mScanFilters = null;
+ }
}
}
@@ -204,6 +213,20 @@
onStart();
}
+ @Override
+ protected void onSetScanFilters(List<android.nearby.ScanFilter> filters) {
+ synchronized (mLock) {
+ mScanFilters = filters == null ? null : List.copyOf(filters);
+ }
+ }
+
+ @VisibleForTesting
+ protected List<android.nearby.ScanFilter> getFiltersLocked() {
+ synchronized (mLock) {
+ return mScanFilters == null ? null : List.copyOf(mScanFilters);
+ }
+ }
+
private void startScan(
List<ScanFilter> scanFilters, ScanSettings scanSettings,
android.bluetooth.le.ScanCallback scanCallback) {
@@ -253,24 +276,29 @@
private void setPresenceDevice(byte[] data, NearbyDeviceParcelable.Builder builder,
String deviceName, int rssi) {
- for (android.nearby.ScanFilter scanFilter : mScanFilters) {
- if (scanFilter instanceof PresenceScanFilter) {
- // Iterate all possible authenticity key and identity combinations to decrypt
- // advertisement
- PresenceScanFilter presenceFilter = (PresenceScanFilter) scanFilter;
- for (PublicCredential credential : presenceFilter.getCredentials()) {
- ExtendedAdvertisement advertisement =
- ExtendedAdvertisement.fromBytes(data, credential);
- if (advertisement == null) {
- continue;
- }
- if (CryptorImpIdentityV1.getInstance().verify(
- advertisement.getIdentity(),
- credential.getEncryptedMetadataKeyTag())) {
- builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName,
- rssi));
- builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag());
- return;
+ synchronized (mLock) {
+ if (mScanFilters == null) {
+ return;
+ }
+ for (android.nearby.ScanFilter scanFilter : mScanFilters) {
+ if (scanFilter instanceof PresenceScanFilter) {
+ // Iterate all possible authenticity key and identity combinations to decrypt
+ // advertisement
+ PresenceScanFilter presenceFilter = (PresenceScanFilter) scanFilter;
+ for (PublicCredential credential : presenceFilter.getCredentials()) {
+ ExtendedAdvertisement advertisement =
+ ExtendedAdvertisement.fromBytes(data, credential);
+ if (advertisement == null) {
+ continue;
+ }
+ if (CryptorImpIdentityV1.getInstance().verify(
+ advertisement.getIdentity(),
+ credential.getEncryptedMetadataKeyTag())) {
+ builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName,
+ rssi));
+ builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag());
+ return;
+ }
}
}
}
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
index 611f475..93acede 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
@@ -35,6 +35,7 @@
import android.nearby.ScanFilter;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.NearbyConfiguration;
@@ -42,6 +43,7 @@
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Executor;
import service.proto.Blefilter;
@@ -66,11 +68,17 @@
private final ChreCommunication mChreCommunication;
private final ChreCallback mChreCallback;
+ private final Object mLock = new Object();
+
private boolean mChreStarted = false;
private Blefilter.BleFilters mFilters = null;
private Context mContext;
private NearbyConfiguration mNearbyConfiguration;
private final IntentFilter mIntentFilter;
+ // Null when the filters are never set
+ @GuardedBy("mLock")
+ @Nullable
+ private List<ScanFilter> mScanFilters;
private final BroadcastReceiver mScreenBroadcastReceiver =
new BroadcastReceiver() {
@@ -102,20 +110,28 @@
@Override
protected void onStart() {
Log.d(TAG, "Start CHRE scan");
- updateFilters();
+ synchronized (mLock) {
+ updateFiltersLocked();
+ }
}
@Override
protected void onStop() {
Log.d(TAG, "Stop CHRE scan");
- mScanFilters.clear();
- updateFilters();
+ synchronized (mLock) {
+ if (mScanFilters != null) {
+ mScanFilters = null;
+ }
+ updateFiltersLocked();
+ }
}
@Override
- protected void invalidateScanMode() {
- onStop();
- onStart();
+ protected void onSetScanFilters(List<ScanFilter> filters) {
+ synchronized (mLock) {
+ mScanFilters = filters == null ? null : List.copyOf(filters);
+ updateFiltersLocked();
+ }
}
/**
@@ -127,7 +143,15 @@
return mChreCommunication.available();
}
- private synchronized void updateFilters() {
+ @VisibleForTesting
+ List<ScanFilter> getFiltersLocked() {
+ synchronized (mLock) {
+ return mScanFilters == null ? null : List.copyOf(mScanFilters);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updateFiltersLocked() {
if (mScanFilters == null) {
Log.e(TAG, "ScanFilters not set.");
return;
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index b7574c9..41d5686 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -57,7 +57,9 @@
protected final Object mLock = new Object();
private final Context mContext;
private final BleDiscoveryProvider mBleDiscoveryProvider;
- @Nullable private final ChreDiscoveryProvider mChreDiscoveryProvider;
+ @VisibleForTesting
+ @Nullable
+ final ChreDiscoveryProvider mChreDiscoveryProvider;
private @ScanRequest.ScanMode int mScanMode;
private final Injector mInjector;
@@ -351,7 +353,8 @@
mBleDiscoveryProvider.getController().stop();
}
- private void stopChreProvider() {
+ @VisibleForTesting
+ protected void stopChreProvider() {
mChreDiscoveryProvider.getController().stop();
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
index 9531c53..ebb897e 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
@@ -94,7 +94,7 @@
mBleDiscoveryProvider.getController().setProviderScanFilters(filterList);
mBleDiscoveryProvider.onStart();
mBleDiscoveryProvider.onStop();
- assertThat(mBleDiscoveryProvider.mScanFilters).isEmpty();
+ assertThat(mBleDiscoveryProvider.getFiltersLocked()).isNull();
}
@Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
index 8591b60..91a0b56 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
@@ -45,11 +45,17 @@
import org.mockito.MockitoAnnotations;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
public class DiscoveryProviderManagerTest {
private static final int SCAN_MODE_CHRE_ONLY = 3;
private static final int DATA_TYPE_SCAN_MODE = 102;
+ private static final int UID = 1234;
+ private static final int PID = 5678;
+ private static final String PACKAGE_NAME = "android.nearby.test";
@Mock Injector mInjector;
@Mock Context mContext;
@@ -81,6 +87,8 @@
new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
mChreDiscoveryProvider,
mScanTypeScanListenerRecordMap);
+ mCallerIdentity = CallerIdentity
+ .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null);
}
@Test
@@ -216,6 +224,89 @@
assertThat(start).isTrue();
}
+ @Test
+ public void test_stopChreProvider_clearFilters() throws Exception {
+ // Cannot use mocked ChreDiscoveryProvider,
+ // so we cannot use class variable mDiscoveryProviderManager
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ DiscoveryProviderManager manager =
+ new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
+ new ChreDiscoveryProvider(
+ mContext,
+ new ChreCommunication(mInjector, mContext, executor), executor),
+ mScanTypeScanListenerRecordMap);
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+ manager.startChreProvider(List.of(getPresenceScanFilter()));
+ // This is an asynchronized process. The filters will be set in executor thread. So we need
+ // to wait for some time to get the correct result.
+ Thread.sleep(200);
+
+ assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+ .isTrue();
+ assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+
+ manager.stopChreProvider();
+ Thread.sleep(200);
+ // The filters should be cleared right after.
+ assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+ .isFalse();
+ assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNull();
+ }
+
+ @Test
+ public void test_restartChreProvider() throws Exception {
+ // Cannot use mocked ChreDiscoveryProvider,
+ // so we cannot use class variable mDiscoveryProviderManager
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ DiscoveryProviderManager manager =
+ new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
+ new ChreDiscoveryProvider(
+ mContext,
+ new ChreCommunication(mInjector, mContext, executor), executor),
+ mScanTypeScanListenerRecordMap);
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+ manager.startChreProvider(List.of(getPresenceScanFilter()));
+ // This is an asynchronized process. The filters will be set in executor thread. So we need
+ // to wait for some time to get the correct result.
+ Thread.sleep(200);
+
+ assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+ .isTrue();
+ assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+
+ // We want to make sure quickly restart the provider the filters should
+ // be reset correctly.
+ // See b/255922206, there can be a race condition that filters get cleared because onStop()
+ // get executed after onStart() if they are called from different threads.
+ manager.stopChreProvider();
+ manager.mChreDiscoveryProvider.getController().setProviderScanFilters(
+ List.of(getPresenceScanFilter()));
+ manager.startChreProvider(List.of(getPresenceScanFilter()));
+ Thread.sleep(200);
+ assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+ .isTrue();
+ assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+
+ // Wait for enough time
+ Thread.sleep(1000);
+
+ assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
+ .isTrue();
+ assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
+ }
+
private static PresenceScanFilter getPresenceScanFilter() {
final byte[] secretId = new byte[]{1, 2, 3, 4};
final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index 39cbaf7..af0b8d8 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -30,9 +30,11 @@
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
+#include "netdbpf/NetworkTraceHandler.h"
using android::bpf::bpfGetUidStats;
using android::bpf::bpfGetIfaceStats;
+using android::bpf::NetworkTraceHandler;
namespace android {
@@ -67,7 +69,7 @@
}
}
-static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
+static jlong nativeGetTotalStat(JNIEnv* env, jclass clazz, jint type) {
Stats stats = {};
if (bpfGetIfaceStats(NULL, &stats) == 0) {
@@ -77,7 +79,7 @@
}
}
-static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
+static jlong nativeGetIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
ScopedUtfChars iface8(env, iface);
if (iface8.c_str() == NULL) {
return UNKNOWN;
@@ -92,7 +94,7 @@
}
}
-static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
+static jlong nativeGetUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
Stats stats = {};
if (bpfGetUidStats(uid, &stats) == 0) {
@@ -102,10 +104,15 @@
}
}
+static void nativeInitNetworkTracing(JNIEnv* env, jclass clazz) {
+ NetworkTraceHandler::InitPerfettoTracing();
+}
+
static const JNINativeMethod gMethods[] = {
- {"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
- {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
- {"nativeGetUidStat", "(II)J", (void*)getUidStat},
+ {"nativeGetTotalStat", "(I)J", (void*)nativeGetTotalStat},
+ {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)nativeGetIfaceStat},
+ {"nativeGetUidStat", "(II)J", (void*)nativeGetUidStat},
+ {"nativeInitNetworkTracing", "()V", (void*)nativeInitNetworkTracing},
};
int register_android_server_net_NetworkStatsService(JNIEnv* env) {
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index 5b3d314..aa1ee41 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -24,12 +24,19 @@
host_supported: false,
header_libs: ["bpf_connectivity_headers"],
srcs: [
- "BpfNetworkStats.cpp"
+ "BpfNetworkStats.cpp",
+ "NetworkTraceHandler.cpp",
],
shared_libs: [
"libbase",
"liblog",
],
+ static_libs: [
+ "libperfetto_client_experimental",
+ ],
+ export_static_lib_headers: [
+ "libperfetto_client_experimental",
+ ],
export_include_dirs: ["include"],
cflags: [
"-Wall",
@@ -54,6 +61,7 @@
header_libs: ["bpf_connectivity_headers"],
srcs: [
"BpfNetworkStatsTest.cpp",
+ "NetworkTraceHandlerTest.cpp",
],
cflags: [
"-Wall",
@@ -64,10 +72,12 @@
static_libs: [
"libgmock",
"libnetworkstats",
+ "libperfetto_client_experimental",
],
shared_libs: [
"libbase",
"liblog",
+ "libandroid_net",
],
compile_multilib: "both",
multilib: {
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
new file mode 100644
index 0000000..4c37b8d
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "NetworkTrace"
+
+#include "netdbpf/NetworkTraceHandler.h"
+
+#include <arpa/inet.h>
+#include <bpf/BpfUtils.h>
+#include <log/log.h>
+#include <perfetto/config/android/network_trace_config.pbzero.h>
+#include <perfetto/trace/android/network_trace.pbzero.h>
+#include <perfetto/trace/profiling/profile_packet.pbzero.h>
+#include <perfetto/tracing/platform.h>
+#include <perfetto/tracing/tracing.h>
+
+// Note: this is initializing state for a templated Perfetto type that resides
+// in the `perfetto` namespace. This must be defined in the global scope.
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::bpf::NetworkTraceHandler);
+
+namespace android {
+namespace bpf {
+using ::perfetto::protos::pbzero::NetworkPacketEvent;
+using ::perfetto::protos::pbzero::NetworkPacketTraceConfig;
+using ::perfetto::protos::pbzero::TracePacket;
+using ::perfetto::protos::pbzero::TrafficDirection;
+
+// static
+void NetworkTraceHandler::RegisterDataSource() {
+ ALOGD("Registering Perfetto data source");
+ perfetto::DataSourceDescriptor dsd;
+ dsd.set_name("android.network_packets");
+ NetworkTraceHandler::Register(dsd);
+}
+
+// static
+void NetworkTraceHandler::InitPerfettoTracing() {
+ perfetto::TracingInitArgs args = {};
+ args.backends |= perfetto::kSystemBackend;
+ perfetto::Tracing::Initialize(args);
+ NetworkTraceHandler::RegisterDataSource();
+}
+
+NetworkTraceHandler::NetworkTraceHandler()
+ : NetworkTraceHandler([this](const PacketTrace& pkt) {
+ NetworkTraceHandler::Trace(
+ [this, pkt](NetworkTraceHandler::TraceContext ctx) {
+ Fill(pkt, *ctx.NewTracePacket());
+ });
+ }) {}
+
+void NetworkTraceHandler::OnSetup(const SetupArgs& args) {
+ const std::string& raw = args.config->network_packet_trace_config_raw();
+ NetworkPacketTraceConfig::Decoder config(raw);
+
+ mPollMs = config.poll_ms();
+ if (mPollMs < 100) {
+ ALOGI("poll_ms is missing or below the 100ms minimum. Increasing to 100ms");
+ mPollMs = 100;
+ }
+}
+
+void NetworkTraceHandler::OnStart(const StartArgs&) {
+ if (!Start()) return;
+ mTaskRunner = perfetto::Platform::GetDefaultPlatform()->CreateTaskRunner({});
+ Loop();
+}
+
+void NetworkTraceHandler::OnStop(const StopArgs&) {
+ Stop();
+ mTaskRunner.reset();
+}
+
+void NetworkTraceHandler::Loop() {
+ mTaskRunner->PostDelayedTask([this]() { Loop(); }, mPollMs);
+ ConsumeAll();
+}
+
+void NetworkTraceHandler::Fill(const PacketTrace& src, TracePacket& dst) {
+ dst.set_timestamp(src.timestampNs);
+ auto* event = dst.set_network_packet();
+ event->set_direction(src.egress ? TrafficDirection::DIR_EGRESS
+ : TrafficDirection::DIR_INGRESS);
+ event->set_length(src.length);
+ event->set_uid(src.uid);
+ event->set_tag(src.tag);
+
+ event->set_local_port(src.egress ? ntohs(src.sport) : ntohs(src.dport));
+ event->set_remote_port(src.egress ? ntohs(src.dport) : ntohs(src.sport));
+
+ event->set_ip_proto(src.ipProto);
+ event->set_tcp_flags(src.tcpFlags);
+
+ char ifname[IF_NAMESIZE] = {};
+ if (if_indextoname(src.ifindex, ifname) == ifname) {
+ event->set_interface(std::string(ifname));
+ } else {
+ event->set_interface("error");
+ }
+}
+
+bool NetworkTraceHandler::Start() {
+ ALOGD("Starting datasource");
+
+ auto status = mConfigurationMap.init(PACKET_TRACE_ENABLED_MAP_PATH);
+ if (!status.ok()) {
+ ALOGW("Failed to bind config map: %s", status.error().message().c_str());
+ return false;
+ }
+
+ auto rb = BpfRingbuf<PacketTrace>::Create(PACKET_TRACE_RINGBUF_PATH);
+ if (!rb.ok()) {
+ ALOGW("Failed to create ringbuf: %s", rb.error().message().c_str());
+ return false;
+ }
+
+ mRingBuffer = std::move(*rb);
+
+ auto res = mConfigurationMap.writeValue(0, true, BPF_ANY);
+ if (!res.ok()) {
+ ALOGW("Failed to enable tracing: %s", res.error().message().c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool NetworkTraceHandler::Stop() {
+ ALOGD("Stopping datasource");
+
+ auto res = mConfigurationMap.writeValue(0, false, BPF_ANY);
+ if (!res.ok()) {
+ ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
+ return false;
+ }
+
+ mRingBuffer.reset();
+
+ return true;
+}
+
+bool NetworkTraceHandler::ConsumeAll() {
+ if (mRingBuffer == nullptr) {
+ ALOGW("Tracing is not active");
+ return false;
+ }
+
+ base::Result<int> ret = mRingBuffer->ConsumeAll(mCallback);
+ if (!ret.ok()) {
+ ALOGW("Failed to poll ringbuf: %s", ret.error().message().c_str());
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
new file mode 100644
index 0000000..760ae91
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 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 <android-base/unique_fd.h>
+#include <android/multinetwork.h>
+#include <arpa/inet.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "netdbpf/NetworkTraceHandler.h"
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::Each;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+__be16 bindAndListen(int s) {
+ sockaddr_in sin = {.sin_family = AF_INET};
+ socklen_t len = sizeof(sin);
+ if (bind(s, (sockaddr*)&sin, sizeof(sin))) return 0;
+ if (listen(s, 1)) return 0;
+ if (getsockname(s, (sockaddr*)&sin, &len)) return 0;
+ return sin.sin_port;
+}
+
+// This takes tcp flag constants from the standard library and makes them usable
+// with the flags we get from BPF. The standard library flags are big endian
+// whereas the BPF flags are reported in host byte order. BPF also trims the
+// flags down to the 8 single-bit flag bits (fin, syn, rst, etc).
+constexpr inline uint8_t FlagToHost(__be32 be_unix_flags) {
+ return ntohl(be_unix_flags) >> 16;
+}
+
+// Pretty prints all fields for a list of packets (useful for debugging).
+struct PacketPrinter {
+ const std::vector<PacketTrace>& data;
+ static constexpr char kTcpFlagNames[] = "FSRPAUEC";
+
+ friend std::ostream& operator<<(std::ostream& os, const PacketPrinter& d) {
+ os << "Packet count: " << d.data.size();
+ for (const PacketTrace& info : d.data) {
+ os << "\nifidx=" << info.ifindex;
+ os << ", len=" << info.length;
+ os << ", uid=" << info.uid;
+ os << ", tag=" << info.tag;
+ os << ", sport=" << info.sport;
+ os << ", dport=" << info.dport;
+ os << ", direction=" << (info.egress ? "egress" : "ingress");
+ os << ", proto=" << static_cast<int>(info.ipProto);
+ os << ", ip=" << static_cast<int>(info.ipVersion);
+ os << ", flags=";
+ for (int i = 0; i < 8; i++) {
+ os << ((info.tcpFlags & (1 << i)) ? kTcpFlagNames[i] : '.');
+ }
+ }
+ return os;
+ }
+};
+
+class NetworkTraceHandlerTest : public testing::Test {
+ protected:
+ void SetUp() {
+ if (access(PACKET_TRACE_RINGBUF_PATH, R_OK)) {
+ GTEST_SKIP() << "Network tracing is not enabled/loaded on this build";
+ }
+ }
+};
+
+TEST_F(NetworkTraceHandlerTest, PollWhileInactive) {
+ NetworkTraceHandler handler([&](const PacketTrace& pkt) {});
+
+ // One succeed after start and before stop.
+ EXPECT_FALSE(handler.ConsumeAll());
+ ASSERT_TRUE(handler.Start());
+ EXPECT_TRUE(handler.ConsumeAll());
+ ASSERT_TRUE(handler.Stop());
+ EXPECT_FALSE(handler.ConsumeAll());
+}
+
+TEST_F(NetworkTraceHandlerTest, TraceTcpSession) {
+ __be16 server_port = 0;
+ std::vector<PacketTrace> packets;
+
+ // Record all packets with the bound address and current uid. This callback is
+ // involked only within ConsumeAll, at which point the port should have
+ // already been filled in and all packets have been processed.
+ NetworkTraceHandler handler([&](const PacketTrace& pkt) {
+ if (pkt.sport != server_port && pkt.dport != server_port) return;
+ if (pkt.uid != getuid()) return;
+ packets.push_back(pkt);
+ });
+
+ ASSERT_TRUE(handler.Start());
+ const uint32_t kClientTag = 2468;
+ const uint32_t kServerTag = 1357;
+
+ // Go through a typical connection sequence between two v4 sockets using tcp.
+ // This covers connection handshake, shutdown, and one data packet.
+ {
+ android::base::unique_fd clientsocket(socket(AF_INET, SOCK_STREAM, 0));
+ ASSERT_NE(-1, clientsocket) << "Failed to open client socket";
+ ASSERT_EQ(android_tag_socket(clientsocket, kClientTag), 0);
+
+ android::base::unique_fd serversocket(socket(AF_INET, SOCK_STREAM, 0));
+ ASSERT_NE(-1, serversocket) << "Failed to open server socket";
+ ASSERT_EQ(android_tag_socket(serversocket, kServerTag), 0);
+
+ server_port = bindAndListen(serversocket);
+ ASSERT_NE(0, server_port) << "Can't bind to server port";
+
+ sockaddr_in addr = {.sin_family = AF_INET, .sin_port = server_port};
+ ASSERT_EQ(0, connect(clientsocket, (sockaddr*)&addr, sizeof(addr)))
+ << "connect to loopback failed: " << strerror(errno);
+
+ int accepted = accept(serversocket, nullptr, nullptr);
+ ASSERT_NE(-1, accepted) << "accept connection failed: " << strerror(errno);
+
+ const char data[] = "abcdefghijklmnopqrstuvwxyz";
+ EXPECT_EQ(send(clientsocket, data, sizeof(data), 0), sizeof(data))
+ << "failed to send message: " << strerror(errno);
+
+ char buff[100] = {};
+ EXPECT_EQ(recv(accepted, buff, sizeof(buff), 0), sizeof(data))
+ << "failed to receive message: " << strerror(errno);
+
+ EXPECT_EQ(std::string(data), std::string(buff));
+ }
+
+ ASSERT_TRUE(handler.ConsumeAll());
+ ASSERT_TRUE(handler.Stop());
+
+ // There are 12 packets in total (6 messages: each seen by client & server):
+ // 1. Client connects to server with syn
+ // 2. Server responds with syn ack
+ // 3. Client responds with ack
+ // 4. Client sends data with psh ack
+ // 5. Server acks the data packet
+ // 6. Client closes connection with fin ack
+ ASSERT_EQ(packets.size(), 12) << PacketPrinter{packets};
+
+ // All packets should be TCP packets.
+ EXPECT_THAT(packets, Each(Field(&PacketTrace::ipProto, Eq(IPPROTO_TCP))));
+
+ // Packet 1: client requests connection with server.
+ EXPECT_EQ(packets[0].egress, 1) << PacketPrinter{packets};
+ EXPECT_EQ(packets[0].dport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[0].tag, kClientTag) << PacketPrinter{packets};
+ EXPECT_EQ(packets[0].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+ << PacketPrinter{packets};
+
+ // Packet 2: server receives request from client.
+ EXPECT_EQ(packets[1].egress, 0) << PacketPrinter{packets};
+ EXPECT_EQ(packets[1].dport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[1].tag, kServerTag) << PacketPrinter{packets};
+ EXPECT_EQ(packets[1].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+ << PacketPrinter{packets};
+
+ // Packet 3: server replies back with syn ack.
+ EXPECT_EQ(packets[2].egress, 1) << PacketPrinter{packets};
+ EXPECT_EQ(packets[2].sport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[2].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+ << PacketPrinter{packets};
+
+ // Packet 4: client receives the server's syn ack.
+ EXPECT_EQ(packets[3].egress, 0) << PacketPrinter{packets};
+ EXPECT_EQ(packets[3].sport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[3].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+ << PacketPrinter{packets};
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
new file mode 100644
index 0000000..c257aa0
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#pragma once
+
+#include <perfetto/base/task_runner.h>
+#include <perfetto/tracing.h>
+
+#include <string>
+#include <unordered_map>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfRingbuf.h"
+
+// For PacketTrace struct definition
+#include "netd.h"
+
+namespace android {
+namespace bpf {
+
+class NetworkTraceHandler : public perfetto::DataSource<NetworkTraceHandler> {
+ public:
+ // Registers this DataSource.
+ static void RegisterDataSource();
+
+ // Connects to the system Perfetto daemon and registers the trace handler.
+ static void InitPerfettoTracing();
+
+ // Initialize with the default Perfetto callback.
+ NetworkTraceHandler();
+
+ // Testonly: initialize with a callback capable of intercepting data.
+ NetworkTraceHandler(std::function<void(const PacketTrace&)> callback)
+ : mCallback(std::move(callback)) {}
+
+ // Testonly: standalone functions without perfetto dependency.
+ bool Start();
+ bool Stop();
+ bool ConsumeAll();
+
+ // perfetto::DataSource overrides:
+ void OnSetup(const SetupArgs&) override;
+ void OnStart(const StartArgs&) override;
+ void OnStop(const StopArgs&) override;
+
+ // Convert a PacketTrace into a Perfetto trace packet.
+ void Fill(const PacketTrace& src,
+ ::perfetto::protos::pbzero::TracePacket& dst);
+
+ private:
+ void Loop();
+
+ // How often to poll the ring buffer, defined by the trace config.
+ uint32_t mPollMs;
+
+ // The function to process PacketTrace, typically a Perfetto sink.
+ std::function<void(const PacketTrace&)> mCallback;
+
+ // The BPF ring buffer handle.
+ std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer;
+
+ // The packet tracing config map (really a 1-element array).
+ BpfMap<uint32_t, bool> mConfigurationMap;
+
+ // This must be the last member, causing it to be the first deleted. If it is
+ // not, members required for callbacks can be deleted before it's stopped.
+ std::unique_ptr<perfetto::base::TaskRunner> mTaskRunner;
+};
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/src/com/android/server/NetworkStatsServiceInitializer.java b/service-t/src/com/android/server/NetworkStatsServiceInitializer.java
index 0ea126a..82a4fbd 100644
--- a/service-t/src/com/android/server/NetworkStatsServiceInitializer.java
+++ b/service-t/src/com/android/server/NetworkStatsServiceInitializer.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.net.TrafficStats;
+import android.os.Build;
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
@@ -46,6 +47,15 @@
/* allowIsolated= */ false);
TrafficStats.init(getContext());
}
+
+ // The following code registers the Perfetto Network Trace Handler on non-user builds.
+ // The enhanced tracing is intended to be used for debugging and diagnosing issues. This
+ // is conditional on the build type rather than `isDebuggable` to match the system_server
+ // selinux rules which only allow the Perfetto connection under the same circumstances.
+ if (SdkLevel.isAtLeastU() && !Build.TYPE.equals("user")) {
+ Log.i(TAG, "Initializing network tracing hooks");
+ NetworkStatsService.nativeInitNetworkTracing();
+ }
}
@Override
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index b361720..5dcf860 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -919,6 +919,12 @@
// interfaces that do not have an associated Network.
break;
}
+ if (foundNetId == INetd.DUMMY_NET_ID) {
+ // Ignore services on the dummy0 interface: they are only seen when
+ // discovering locally advertised services, and are not reachable
+ // through that interface.
+ break;
+ }
setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
clientInfo.onServiceFound(clientId, servInfo);
break;
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 5852a30..4eeaf6b 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -3269,4 +3269,7 @@
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
private static native long nativeGetUidStat(int uid, int type);
+
+ /** Initializes and registers the Perfetto Network Trace data source */
+ public static native void nativeInitNetworkTracing();
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index b9d2760..f5c6fb7 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -6245,9 +6245,7 @@
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
- if (!mProfileNetworkPreferences.isEmpty()) {
- updateProfileAllowedNetworks();
- }
+ updateProfileAllowedNetworks();
}
private void onUserRemoved(@NonNull final UserHandle user) {
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index d220835..27be545 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.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.CollectionUtils;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SocketUtils;
@@ -76,7 +77,7 @@
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
/**
@@ -333,13 +334,10 @@
*/
public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
mKeepaliveTracker.handleStopAllKeepalives(nai, reason);
- final Iterator<AutomaticOnOffKeepalive> iterator = mAutomaticOnOffKeepalives.iterator();
- while (iterator.hasNext()) {
- final AutomaticOnOffKeepalive autoKi = iterator.next();
- if (autoKi.mKi.getNai() == nai) {
- cleanupAutoOnOffKeepalive(autoKi);
- iterator.remove();
- }
+ final List<AutomaticOnOffKeepalive> matches =
+ CollectionUtils.filter(mAutomaticOnOffKeepalives, it -> it.mKi.getNai() == nai);
+ for (final AutomaticOnOffKeepalive ki : matches) {
+ cleanupAutoOnOffKeepalive(ki);
}
}
@@ -399,7 +397,6 @@
if (autoKi == null) return;
cleanupAutoOnOffKeepalive(autoKi);
- mAutomaticOnOffKeepalives.remove(autoKi);
}
private void cleanupAutoOnOffKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi) {
@@ -407,6 +404,7 @@
mAlarmManager.cancel(autoKi.mTcpPollingAlarm);
// Close the duplicated fd that maintains the lifecycle of socket.
FileUtils.closeQuietly(autoKi.mFd);
+ mAutomaticOnOffKeepalives.remove(autoKi);
}
/**
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 61b597a..f596b79 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -96,6 +96,7 @@
import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
+import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.MiscAsserts.assertThrows;
@@ -1131,7 +1132,8 @@
final ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
- mContext.registerReceiver(receiver, filter);
+ final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
+ mContext.registerReceiver(receiver, filter, flags);
// Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
final Intent intent = new Intent(NETWORK_CALLBACK_ACTION)
@@ -1225,7 +1227,8 @@
networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
}
};
- mContext.registerReceiver(receiver, filter);
+ final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
+ mContext.registerReceiver(receiver, filter, flags);
final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
try {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 17e769c..c9783ba 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -15760,6 +15760,39 @@
}
@Test
+ public void testProfileNetworkPreferenceBlocking_addUser() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
+
+ // Only one network
+ mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellAgent.connect(true);
+
+ // Verify uid ranges 0~99999 are allowed
+ final ArraySet<UidRange> allowedRanges = new ArraySet<>();
+ allowedRanges.add(PRIMARY_UIDRANGE);
+ final NativeUidRangeConfig config1User = new NativeUidRangeConfig(
+ mCellAgent.getNetwork().netId,
+ toUidRangeStableParcels(allowedRanges),
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[] { config1User });
+
+ doReturn(asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE))
+ .when(mUserManager).getUserHandles(anyBoolean());
+ final Intent addedIntent = new Intent(ACTION_USER_ADDED);
+ addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(SECONDARY_USER));
+ processBroadcast(addedIntent);
+
+ // Make sure the allow list has been updated.
+ allowedRanges.add(UidRange.createForUser(SECONDARY_USER_HANDLE));
+ final NativeUidRangeConfig config2Users = new NativeUidRangeConfig(
+ mCellAgent.getNetwork().netId,
+ toUidRangeStableParcels(allowedRanges),
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[] { config2Users });
+ }
+
+ @Test
public void testProfileNetworkPreferenceBlocking_changePreference() throws Exception {
final InOrder inOrder = inOrder(mMockNetd);
final UserHandle testHandle = setupEnterpriseNetwork();
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index c7a6639..98a8ed2 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -413,6 +413,35 @@
}
@Test
+ public void testDiscoverOnBlackholeNetwork() throws Exception {
+ final NsdManager client = connectClient(mService);
+ final DiscoveryListener discListener = mock(DiscoveryListener.class);
+ client.discoverServices(SERVICE_TYPE, PROTOCOL, discListener);
+ waitForIdle();
+
+ final IMDnsEventListener eventListener = getEventListener();
+ final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(SERVICE_TYPE),
+ eq(0) /* interfaceIdx */);
+ // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
+ // this needs to use a timeout
+ verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
+
+ final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
+ discIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_FOUND,
+ SERVICE_NAME,
+ SERVICE_TYPE,
+ DOMAIN_NAME,
+ 123 /* interfaceIdx */,
+ INetd.DUMMY_NET_ID); // netId of the blackhole network
+ eventListener.onServiceDiscoveryStatus(discoveryInfo);
+ waitForIdle();
+
+ verify(discListener, never()).onServiceFound(any());
+ }
+
+ @Test
public void testServiceRegistrationSuccessfulAndFailed() throws Exception {
final NsdManager client = connectClient(mService);
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index 4d4a0b2..c881767 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -29,11 +29,13 @@
],
libs: [
"androidx.annotation_annotation",
+ "framework-annotations-lib",
],
sdk_version: "module_current",
}
// GN: //components/cronet/android:cronet_api_java
+// TODO(danstahr): add the API helpers separately after the main API is checked in and thoroughly reviewed
filegroup {
name: "cronet_aml_api_sources",
srcs: [
@@ -59,18 +61,6 @@
"components/cronet/android/api/src/android/net/http/UploadDataSink.java",
"components/cronet/android/api/src/android/net/http/UrlRequest.java",
"components/cronet/android/api/src/android/net/http/UrlResponseInfo.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/ByteArrayCallback.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/ContentTypeParametersParser.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/HttpResponse.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/ImplicitFlowControlCallback.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/InMemoryTransformCallback.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/JsonCallback.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/RedirectHandler.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/RedirectHandlers.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/RequestCompletionListener.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/StringCallback.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/UploadDataProviders.java",
- "components/cronet/android/api/src/android/net/http/apihelpers/UrlRequestCallbacks.java",
],
}
@@ -2469,8 +2459,8 @@
"third_party/protobuf/src/",
],
cpp_std: "c++17",
- linker_scripts: [
- "base/android/library_loader/anchor_functions.lds",
+ ldflags: [
+ "-Wl,--script,external/cronet/base/android/library_loader/anchor_functions.lds",
],
stem: "libcronet.108.0.5359.128",
target: {
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index 7829694..6ae3609 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -128,12 +128,17 @@
"-msse4.2",
]
+def get_linker_script_ldflag(script_path):
+ return f'-Wl,--script,{tree_path}/{script_path}'
+
# Additional arguments to apply to Android.bp rules.
additional_args = {
# TODO: remove if not needed.
'cronet_aml_components_cronet_android_cronet': [
- ('linker_scripts', {
- 'base/android/library_loader/anchor_functions.lds',
+ # linker_scripts property is not available in tm-mainline-prod.
+ # So use ldflags to specify linker script.
+ ('ldflags',{
+ get_linker_script_ldflag('base/android/library_loader/anchor_functions.lds'),
}),
],
'cronet_aml_net_net': [
@@ -370,6 +375,7 @@
self.min_sdk_version = None
self.proto = dict()
self.linker_scripts = set()
+ self.ldflags = set()
# The genrule_XXX below are properties that must to be propagated back
# on the module(s) that depend on the genrule.
self.genrule_headers = set()
@@ -440,6 +446,7 @@
self._output_field(output, 'stubs')
self._output_field(output, 'proto')
self._output_field(output, 'linker_scripts')
+ self._output_field(output, 'ldflags')
self._output_field(output, 'cppflags')
self._output_field(output, 'libs')
self._output_field(output, 'stem')
@@ -1557,8 +1564,12 @@
def create_java_api_module(blueprint, gn):
source_module = Module('filegroup', module_prefix + 'api_sources', java_api_target_name)
+ # TODO add the API helpers separately after the main API is checked in and thoroughly reviewed
source_module.srcs.update([gn_utils.label_to_path(source)
- for source in get_api_java_sources(gn)])
+ for source in get_api_java_sources(gn)
+ if "apihelpers" not in source])
+ source_module.comment += "\n// TODO(danstahr): add the API helpers separately after the main" \
+ " API is checked in and thoroughly reviewed"
source_module.srcs.update([
':' + create_action_module(blueprint, gn.get_target(dep), 'java_genrule').name
for dep in get_api_java_actions(gn)])
@@ -1569,6 +1580,7 @@
java_module.sdk_version = "module_current"
java_module.libs = {
"androidx.annotation_annotation",
+ "framework-annotations-lib",
}
blueprint.add_module(java_module)
return java_module
diff --git a/tools/gn2bp/gen_desc_json.sh b/tools/gn2bp/gen_desc_json.sh
index ed684b3..6d1a4f2 100755
--- a/tools/gn2bp/gen_desc_json.sh
+++ b/tools/gn2bp/gen_desc_json.sh
@@ -2,11 +2,34 @@
set -x
# Run this script inside a full chromium checkout.
-# TODO: add support for applying local patches.
OUT_PATH="out/cronet"
#######################################
+# Apply patches in external/cronet.
+# Globals:
+# ANDROID_BUILD_TOP
+# Arguments:
+# None
+#######################################
+function apply_patches() {
+ local -r patch_root="${ANDROID_BUILD_TOP}/external/cronet/patches"
+
+ local upstream_patches
+ upstream_patches=$(ls "${patch_root}/upstream-next")
+ local patch
+ for patch in ${upstream_patches}; do
+ git am --3way "${patch_root}/upstream-next/${patch}"
+ done
+
+ local local_patches
+ local_patches=$(ls "${patch_root}/local")
+ for patch in ${local_patches}; do
+ git am --3way "${patch_root}/local/${patch}"
+ done
+}
+
+#######################################
# Generate desc.json for a specified architecture.
# Globals:
# OUT_PATH
@@ -48,6 +71,7 @@
gn desc "${OUT_PATH}" --format=json --all-toolchains "//*" > "${out_file}"
}
+apply_patches
gn_desc x86
gn_desc x64
gn_desc arm