Merge changes from topic "statsaccess"

* changes:
  Check MAINLINE_NETWORK_STACK as well to make GTS can access proper stats
  Move checkAnyPermissionOf to PermissionUtils
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
index e4949d3..422f4d5 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -34,13 +34,11 @@
 import android.net.http.HttpException;
 import android.net.http.InlineExecutionProhibitedException;
 import android.net.http.UploadDataProvider;
-import android.net.http.UploadDataSink;
 import android.net.http.UrlRequest;
 import android.net.http.UrlRequest.Status;
 import android.net.http.UrlResponseInfo;
 import android.net.http.cts.util.HttpCtsTestServer;
 import android.net.http.cts.util.TestStatusListener;
-import android.net.http.cts.util.TestUploadDataProvider;
 import android.net.http.cts.util.TestUrlRequestCallback;
 import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
 import android.net.http.cts.util.UploadDataProviders;
@@ -140,14 +138,11 @@
     }
 
     @Test
-    public void testUrlRequestPost_EchoRequestBody() throws Exception {
+    public void testUrlRequestPost_EchoRequestBody() {
         String testData = "test";
         UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl());
 
-        TestUploadDataProvider dataProvider =
-                new TestUploadDataProvider(
-                        TestUploadDataProvider.SuccessCallbackMode.SYNC, mCallback.getExecutor());
-        dataProvider.addRead(testData.getBytes());
+        UploadDataProvider dataProvider = UploadDataProviders.create(testData);
         builder.setUploadDataProvider(dataProvider, mCallback.getExecutor());
         builder.addHeader("Content-Type", "text/html");
         builder.build().start();
@@ -155,7 +150,6 @@
 
         assertOKStatusCode(mCallback.mResponseInfo);
         assertEquals(testData, mCallback.mResponseAsString);
-        dataProvider.assertClosed();
     }
 
     @Test
@@ -170,7 +164,7 @@
         callback.setAllowDirectExecutor(true);
         UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
                 mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
-        UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
+        UploadDataProvider dataProvider = UploadDataProviders.create("test");
         builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR);
         builder.addHeader("Content-Type", "text/plain;charset=UTF-8");
         builder.setDirectExecutorAllowed(true);
@@ -193,7 +187,7 @@
 
         UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
                 mTestServer.getEchoBodyUrl(), Executors.newSingleThreadExecutor(), callback);
-        UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
+        UploadDataProvider dataProvider = UploadDataProviders.create("test");
 
         builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR)
                 .addHeader("Content-Type", "text/plain;charset=UTF-8")
@@ -213,7 +207,7 @@
 
         UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
                 mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
-        UploadDataProvider dataProvider = InMemoryUploadDataProvider.fromUtf8String("test");
+        UploadDataProvider dataProvider = UploadDataProviders.create("test");
 
         builder.setUploadDataProvider(dataProvider, Executors.newSingleThreadExecutor())
                 .addHeader("Content-Type", "text/plain;charset=UTF-8")
@@ -415,39 +409,4 @@
             throw new UnsupportedOperationException();
         }
     }
-
-    private static class InMemoryUploadDataProvider extends UploadDataProvider {
-        private final byte[] mBody;
-        private int mNextChunkStartIndex = 0;
-
-        private InMemoryUploadDataProvider(byte[] body) {
-            this.mBody = body;
-        }
-
-        static InMemoryUploadDataProvider fromUtf8String(String body) {
-            return new InMemoryUploadDataProvider(body.getBytes(StandardCharsets.UTF_8));
-        }
-
-        @Override
-        public long getLength() {
-            return mBody.length;
-        }
-
-        @Override
-        public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
-            if (mNextChunkStartIndex >= getLength()) {
-                throw new IllegalStateException("Body of known length is exhausted");
-            }
-            int nextChunkSize =
-                    Math.min(mBody.length - mNextChunkStartIndex, byteBuffer.remaining());
-            byteBuffer.put(mBody, mNextChunkStartIndex, nextChunkSize);
-            mNextChunkStartIndex += nextChunkSize;
-            uploadDataSink.onReadSucceeded(false);
-        }
-
-        @Override
-        public void rewind(UploadDataSink uploadDataSink) {
-            mNextChunkStartIndex = 0;
-        }
-    }
 }
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
deleted file mode 100644
index d047828..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUploadDataProvider.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * 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/UploadDataProviders.java b/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
index 889f8f2..3b90fa0 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Provides implementations of {@link UploadDataProvider} for common use cases. Corresponds to
@@ -91,6 +92,16 @@
         return create(data, 0, data.length);
     }
 
+    /**
+     * Uploads the UTF-8 representation of {@code data}
+     *
+     * @param data String containing data to upload
+     * @return A new UploadDataProvider for the given data
+     */
+    public static UploadDataProvider create(String data) {
+        return create(data.getBytes(StandardCharsets.UTF_8));
+    }
+
     private interface FileChannelProvider {
         FileChannel getChannel() throws IOException;
     }
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index adbc384..8fec6f3 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -48,8 +48,12 @@
     libs: [
         "android.test.base",
         // Needed for direct access to tethering's hidden apis and to avoid `symbol not found`
-        //  errors on some builds.
+        // errors on some builds.
         "framework-tethering.impl",
+        // android.net.Network apis
+        "framework-connectivity",
+        // android.net.TrafficStats apis
+        "framework-connectivity-t",
     ],
     lint: { test: true }
 }
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
index d1d062a..cf3a017 100644
--- a/Cronet/tests/mts/jarjar_excludes.txt
+++ b/Cronet/tests/mts/jarjar_excludes.txt
@@ -8,4 +8,6 @@
 # Do not jarjar the tests and its utils as they also do JNI with cronet_tests.so
 org\.chromium\.net\..*Test.*(\$.+)?
 org\.chromium\.net\.NativeTestServer(\$.+)?
-org\.chromium\.net\.MockUrlRequestJobFactory(\$.+)?
\ No newline at end of file
+org\.chromium\.net\.MockUrlRequestJobFactory(\$.+)?
+org\.chromium\.net\.QuicTestServer(\$.+)?
+org\.chromium\.net\.MockCertVerifier(\$.+)?
\ No newline at end of file
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 70c5f85..d2f6d6a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -75,9 +75,6 @@
       "name": "connectivity_native_test"
     },
     {
-      "name": "CtsNetHttpTestCases"
-    },
-    {
       "name": "libclat_test"
     },
     {
@@ -97,6 +94,16 @@
     },
     {
       "name": "FrameworksNetIntegrationTests"
+    },
+    // Runs both NetHttpTests and CtsNetHttpTestCases
+    {
+      "name": "NetHttpCoverageTests",
+      "options": [
+        {
+          // These sometimes take longer than 1 min which is the presubmit timeout
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
@@ -113,11 +120,6 @@
     },
     {
       "name": "FrameworksNetDeflakeTest"
-    },
-    // Run in postsubmit to confirm test passes continously since this is a new test setup
-    // TODO: move tests to presubmit
-    {
-      "name": "NetHttpTests"
     }
   ],
   "mainline-presubmit": [
@@ -215,7 +217,13 @@
       "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     },
     {
-      "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+      "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      "options": [
+        {
+          // These sometimes take longer than 1 min which is the presubmit timeout
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     }
   ],
   "mainline-postsubmit": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 8810a8c..83ca2b7 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -250,6 +250,9 @@
         // e.g. *classpath_fragments.
         "com.android.tethering",
     ],
+    native_shared_libs: [
+        "libnetd_updatable",
+    ],
 }
 
 java_library_static {
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 69eb58f..007bf23 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -215,6 +215,13 @@
         }
     }
 
+    protected void stopEthernetTethering(final MyTetheringEventCallback callback) {
+        runAsShell(TETHER_PRIVILEGED, () -> {
+            mTm.stopTethering(TETHERING_ETHERNET);
+            maybeUnregisterTetheringEventCallback(callback);
+        });
+    }
+
     protected void cleanUp() throws Exception {
         setPreferTestNetworks(false);
 
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 12ac454..55854e2 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -364,7 +364,7 @@
             // Enable Ethernet tethering and check that it starts.
             tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
         } finally {
-            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+            stopEthernetTethering(tetheringEventCallback);
         }
         // There is nothing more we can do on a physical interface without connecting an actual
         // client, which is not possible in this test.
diff --git a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java b/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
index cb57d13..c2bc812 100644
--- a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
+++ b/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
@@ -80,8 +80,8 @@
     // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
     private static final int RX_UDP_PACKET_SIZE = 30;
     private static final int RX_UDP_PACKET_COUNT = 456;
-    // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
-    private static final int TX_UDP_PACKET_SIZE = 44;
+    // Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+    private static final int TX_UDP_PACKET_SIZE = 30;
     private static final int TX_UDP_PACKET_COUNT = 123;
 
     private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index a8612df..56ace19 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -232,13 +232,13 @@
     // This would require a much newer kernel with newer ebpf accessors.
     // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
     uint64_t packets = 1;
-    uint64_t bytes = skb->len;
-    if (bytes > v->pmtu) {
-        const int tcp_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
-        const int mss = v->pmtu - tcp_overhead;
-        const uint64_t payload = bytes - tcp_overhead;
+    uint64_t L3_bytes = skb->len - l2_header_size;
+    if (L3_bytes > v->pmtu) {
+        const int tcp6_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
+        const int mss = v->pmtu - tcp6_overhead;
+        const uint64_t payload = L3_bytes - tcp6_overhead;
         packets = (payload + mss - 1) / mss;
-        bytes = tcp_overhead * packets + payload;
+        L3_bytes = tcp6_overhead * packets + payload;
     }
 
     // Are we past the limit?  If so, then abort...
@@ -247,7 +247,7 @@
     // a packet we let the core stack deal with things.
     // (The core stack needs to handle limits correctly anyway,
     // since we don't offload all traffic in both directions)
-    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
+    if (stat_v->rxBytes + stat_v->txBytes + L3_bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
 
     if (!is_ethernet) {
         // Try to inject an ethernet header, and simply return if we fail.
@@ -287,7 +287,7 @@
     bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
 
     __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
-    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
+    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
 
     // Overwrite any mac header with the new one
     // For a rawip tx interface it will simply be a bunch of zeroes and later stripped.
@@ -449,13 +449,13 @@
     // This would require a much newer kernel with newer ebpf accessors.
     // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
     uint64_t packets = 1;
-    uint64_t bytes = skb->len;
-    if (bytes > v->pmtu) {
-        const int tcp_overhead = sizeof(struct iphdr) + sizeof(struct tcphdr) + 12;
-        const int mss = v->pmtu - tcp_overhead;
-        const uint64_t payload = bytes - tcp_overhead;
+    uint64_t L3_bytes = skb->len - l2_header_size;
+    if (L3_bytes > v->pmtu) {
+        const int tcp4_overhead = sizeof(struct iphdr) + sizeof(struct tcphdr) + 12;
+        const int mss = v->pmtu - tcp4_overhead;
+        const uint64_t payload = L3_bytes - tcp4_overhead;
         packets = (payload + mss - 1) / mss;
-        bytes = tcp_overhead * packets + payload;
+        L3_bytes = tcp4_overhead * packets + payload;
     }
 
     // Are we past the limit?  If so, then abort...
@@ -464,7 +464,7 @@
     // a packet we let the core stack deal with things.
     // (The core stack needs to handle limits correctly anyway,
     // since we don't offload all traffic in both directions)
-    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
+    if (stat_v->rxBytes + stat_v->txBytes + L3_bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
 
     if (!is_ethernet) {
         // Try to inject an ethernet header, and simply return if we fail.
@@ -540,7 +540,7 @@
     if (updatetime) v->last_used = bpf_ktime_get_boot_ns();
 
     __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
-    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
+    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
 
     // Redirect to forwarded interface.
     //
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index f8d7e4c..ffa2857 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -140,7 +140,7 @@
         "//packages/modules/Connectivity/apex",
         "//packages/modules/Connectivity/service", // For R8 only
         "//packages/modules/Connectivity/service-t",
-        "//packages/modules/Connectivity/nearby/service",
+        "//packages/modules/Connectivity/nearby:__subpackages__",
         "//frameworks/base",
 
         // Tests using hidden APIs
@@ -157,7 +157,6 @@
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-        "//packages/modules/Connectivity/nearby:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index b3c70cf..33bd884 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -61,6 +61,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -183,6 +184,23 @@
         }
     }
 
+    private static Set<String> setOf(@Nullable final String item) {
+        if (item == null) {
+            // Set.of will throw if item is null
+            final Set<String> set = new HashSet<>();
+            set.add(null);
+            return Collections.unmodifiableSet(set);
+        } else {
+            return Set.of(item);
+        }
+    }
+
+    private static void throwAtLeastU() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException("Method not supported on Android U or above");
+        }
+    }
+
     /**
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
      * the given IMSI.
@@ -195,7 +213,7 @@
             publicAlternatives = "Use {@code Builder} instead.")
     public static NetworkTemplate buildTemplateMobileAll(@NonNull String subscriberId) {
         return new NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
-                .setSubscriberIds(Set.of(subscriberId)).build();
+                .setSubscriberIds(setOf(subscriberId)).build();
     }
 
     /**
@@ -259,10 +277,9 @@
     // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate buildTemplateBluetooth() {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "buildTemplateBluetooth is not supported on Android U devices or above");
-        }
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
         return new NetworkTemplate.Builder(MATCH_BLUETOOTH).build();
     }
 
@@ -275,10 +292,9 @@
     // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate buildTemplateProxy() {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "buildTemplateProxy is not supported on Android U devices or above");
-        }
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
         return new NetworkTemplate(MATCH_PROXY, null, null);
     }
 
@@ -290,12 +306,10 @@
     // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "buildTemplateCarrierMetered is not supported on Android U devices or above");
-        }
+        throwAtLeastU();
         return new NetworkTemplate.Builder(MATCH_CARRIER)
-                // Set.of will throw if subscriberId is null
+                // Set.of will throw if subscriberId is null, which is the historical
+                // behavior and should be preserved.
                 .setSubscriberIds(Set.of(subscriberId))
                 .setMeteredness(METERED_YES)
                 .build();
@@ -312,10 +326,7 @@
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
             int ratType, int metered) {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException("buildTemplateMobileWithRatType is not "
-                    + "supported on Android U devices or above");
-        }
+        throwAtLeastU();
         return new NetworkTemplate.Builder(MATCH_MOBILE)
                 .setSubscriberIds(TextUtils.isEmpty(subscriberId)
                         ? Collections.emptySet()
@@ -325,7 +336,6 @@
                 .build();
     }
 
-
     /**
      * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
      * given key of the wifi network.
@@ -337,12 +347,12 @@
     // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException("buildTemplateWifi is not "
-                    + "supported on Android U devices or above");
-        }
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
         return new NetworkTemplate.Builder(MATCH_WIFI)
-                // Set.of will throw if wifiNetworkKey is null
+                // Set.of will throw if wifiNetworkKey is null, which is the historical
+                // behavior and should be preserved.
                 .setWifiNetworkKeys(Set.of(wifiNetworkKey))
                 .build();
     }
@@ -364,14 +374,9 @@
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey,
             @Nullable String subscriberId) {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException("buildTemplateWifi is not "
-                    + "supported on Android U devices or above");
-        }
+        throwAtLeastU();
         return new NetworkTemplate.Builder(MATCH_WIFI)
-                .setSubscriberIds(subscriberId == null
-                        ? Collections.emptySet()
-                        : Set.of(subscriberId))
+                .setSubscriberIds(setOf(subscriberId))
                 .setWifiNetworkKeys(wifiNetworkKey == null
                         ? Collections.emptySet()
                         : Set.of(wifiNetworkKey))
@@ -410,12 +415,20 @@
             // subscriber ID.
             case MATCH_CARRIER:
                 if (matchSubscriberIds.length == 0) {
-                    throw new IllegalArgumentException("checkValidMatchSubscriberIds with empty"
-                            + " list of ids for rule" + getMatchRuleName(matchRule));
+                    throw new IllegalArgumentException("matchSubscriberIds may not contain"
+                            + " null for rule " + getMatchRuleName(matchRule));
                 }
-                // fall through
-            case MATCH_MOBILE:
                 if (CollectionUtils.contains(matchSubscriberIds, null)) {
+                    throw new IllegalArgumentException("matchSubscriberIds may not contain"
+                            + " null for rule " + getMatchRuleName(matchRule));
+                }
+                break;
+            case MATCH_MOBILE:
+                // Prevent from crash for b/273963543, where the OEMs still call into unsupported
+                // buildTemplateMobileAll with null subscriberId and get crashed.
+                final int firstSdk = Build.VERSION.DEVICE_INITIAL_SDK_INT;
+                if (firstSdk > Build.VERSION_CODES.TIRAMISU
+                        && CollectionUtils.contains(matchSubscriberIds, null)) {
                     throw new IllegalArgumentException("checkValidMatchSubscriberIds list of ids"
                             + " may not contain null for rule " + getMatchRuleName(matchRule));
                 }
@@ -448,10 +461,6 @@
         if (matchRule == 6 || matchRule == 7) {
             Log.e(TAG, "Use MATCH_MOBILE with empty subscriberIds or MATCH_WIFI with empty "
                     + "wifiNetworkKeys instead of template with matchRule=" + matchRule);
-            if (SdkLevel.isAtLeastU()) {
-                throw new UnsupportedOperationException(
-                        "Wildcard templates are not supported on Android U devices or above");
-            }
         }
     }
 
@@ -485,10 +494,9 @@
                 getMeterednessForBackwardsCompatibility(matchRule),
                 ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
                 OEM_MANAGED_ALL);
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "This constructor is not supported on Android U devices or above");
-        }
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
     }
 
     /** @hide */
@@ -503,10 +511,7 @@
         this(getBackwardsCompatibleMatchRule(matchRule),
                 matchSubscriberIds == null ? new String[]{} : matchSubscriberIds,
                 matchWifiNetworkKeys, metered, roaming, defaultNetwork, ratType, oemManaged);
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "This constructor is not supported on Android U devices or above");
-        }
+        throwAtLeastU();
     }
 
     /** @hide */
@@ -613,10 +618,9 @@
     //  including in OEM code which can access this by linking against the framework.
     /** @hide */
     public boolean isMatchRuleMobile() {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "isMatchRuleMobile is not supported on Android U devices or above");
-        }
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
         switch (mMatchRule) {
             case MATCH_MOBILE:
             // Old MATCH_MOBILE_WILDCARD
@@ -958,10 +962,7 @@
     // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
     //  including in OEM code which can access this by linking against the framework.
     public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
-        if (SdkLevel.isAtLeastU()) {
-            throw new UnsupportedOperationException(
-                    "normalize is not supported on Android U devices or above");
-        }
+        throwAtLeastU();
         return normalizeImpl(template, mergedList);
     }
 
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 96f2f80..d119db6 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -281,6 +281,9 @@
         EVENT_NAMES.put(UNREGISTER_SERVICE_CALLBACK, "UNREGISTER_SERVICE_CALLBACK");
         EVENT_NAMES.put(UNREGISTER_SERVICE_CALLBACK_SUCCEEDED,
                 "UNREGISTER_SERVICE_CALLBACK_SUCCEEDED");
+        EVENT_NAMES.put(MDNS_DISCOVERY_MANAGER_EVENT, "MDNS_DISCOVERY_MANAGER_EVENT");
+        EVENT_NAMES.put(REGISTER_CLIENT, "REGISTER_CLIENT");
+        EVENT_NAMES.put(UNREGISTER_CLIENT, "UNREGISTER_CLIENT");
     }
 
     /** @hide */
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index cd2daf0..3cc9c65 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -1134,14 +1134,16 @@
             mTransportTypes =
                     (originalTransportTypes & UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS)
                             | (1 << TRANSPORT_TEST);
-
-            // SubIds are only allowed for Test Networks that only declare TRANSPORT_TEST.
-            setSubscriptionIds(originalSubIds);
         } else {
             // If the test network is restricted, then it may declare any transport.
             mTransportTypes = (originalTransportTypes | (1 << TRANSPORT_TEST));
         }
 
+        if (hasSingleTransport(TRANSPORT_TEST)) {
+            // SubIds are only allowed for Test Networks that only declare TRANSPORT_TEST.
+            setSubscriptionIds(originalSubIds);
+        }
+
         mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
         if (!hasTransport(TRANSPORT_CELLULAR)) {
             mNetworkCapabilities |=
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 5bf2973..7de749c 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -75,3 +75,47 @@
         "//packages/modules/IPsec/tests/iketests",
     ],
 }
+
+// Test building mDNS as a standalone, so that it can be imported into other repositories as-is.
+// The mDNS code is platform code so it should use framework-annotations-lib, contrary to apps that
+// should use sdk_version: "system_current" and only androidx.annotation_annotation. But this build
+// rule verifies that the mDNS code can be built into apps, if code transformations are applied to
+// the annotations.
+// When using "system_current", framework annotations are not available; they would appear as
+// package-private as they are marked as such in the system_current stubs. So build against
+// core_platform and add the stubs manually in "libs". See http://b/147773144#comment7.
+java_library {
+    name: "service-connectivity-mdns-standalone-build-test",
+    sdk_version: "core_platform",
+    srcs: [
+        ":service-mdns-droidstubs",
+        "src/com/android/server/connectivity/mdns/**/*.java",
+    ],
+    exclude_srcs: [
+        "src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
+        "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"
+    ],
+    static_libs: [
+        "net-utils-device-common-mdns-standalone-build-test",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "android_system_stubs_current",
+        "androidx.annotation_annotation",
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+droidstubs {
+    name: "service-mdns-droidstubs",
+    srcs: ["src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"],
+    libs: [
+        "net-utils-device-common-mdns-standalone-build-test",
+        "service-connectivity-tiramisu-pre-jarjar"
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+}
\ No newline at end of file
diff --git a/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp b/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
index 2dbe771..a16757b 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
@@ -170,23 +170,10 @@
     return 0;
 }
 
-static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jint limitUid,
-        jobjectArray limitIfacesObj, jint limitTag) {
-
-    std::vector<std::string> limitIfaces;
-    if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
-        int num = env->GetArrayLength(limitIfacesObj);
-        for (int i = 0; i < num; i++) {
-            jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
-            ScopedUtfChars string8(env, string);
-            if (string8.c_str() != NULL) {
-                limitIfaces.push_back(std::string(string8.c_str()));
-            }
-        }
-    }
+static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats) {
     std::vector<stats_line> lines;
 
-    if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0)
+    if (parseBpfNetworkStatsDetail(&lines) < 0)
         return -1;
 
     return statsLinesToNetworkStats(env, clazz, stats, lines);
@@ -202,8 +189,7 @@
 }
 
 static const JNINativeMethod gMethods[] = {
-        { "nativeReadNetworkStatsDetail",
-                "(Landroid/net/NetworkStats;I[Ljava/lang/String;I)I",
+        { "nativeReadNetworkStatsDetail", "(Landroid/net/NetworkStats;)I",
                 (void*) readNetworkStatsDetail },
         { "nativeReadNetworkStatsDev", "(Landroid/net/NetworkStats;)I",
                 (void*) readNetworkStatsDev },
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 4fbc5f4..cdcb0f8 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -110,12 +110,11 @@
 }
 
 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
-                                       const std::vector<std::string>& limitIfaces, int limitTag,
-                                       int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+                                       const BpfMap<StatsKey, StatsValue>& statsMap,
                                        const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
     int64_t unknownIfaceBytesTotal = 0;
     const auto processDetailUidStats =
-            [lines, &limitIfaces, &limitTag, &limitUid, &unknownIfaceBytesTotal, &ifaceMap](
+            [lines, &unknownIfaceBytesTotal, &ifaceMap](
                     const StatsKey& key,
                     const BpfMap<StatsKey, StatsValue>& statsMap) -> Result<void> {
         char ifname[IFNAMSIZ];
@@ -123,18 +122,6 @@
                                 &unknownIfaceBytesTotal)) {
             return Result<void>();
         }
-        std::string ifnameStr(ifname);
-        if (limitIfaces.size() > 0 &&
-            std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) {
-            // Nothing matched; skip this line.
-            return Result<void>();
-        }
-        if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) {
-            return Result<void>();
-        }
-        if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) {
-            return Result<void>();
-        }
         Result<StatsValue> statsEntry = statsMap.readValue(key);
         if (!statsEntry.ok()) {
             return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
@@ -162,9 +149,7 @@
     return 0;
 }
 
-int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
-                               const std::vector<std::string>& limitIfaces, int limitTag,
-                               int limitUid) {
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines) {
     static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
     static BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
     static BpfMap<StatsKey, StatsValue> statsMapA(STATS_MAP_A_PATH);
@@ -195,8 +180,7 @@
     // TODO: the above comment feels like it may be obsolete / out of date,
     // since we no longer swap the map via netd binder rpc - though we do
     // still swap it.
-    int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid,
-                                                 *inactiveStatsMap, ifaceIndexNameMap);
+    int ret = parseBpfNetworkStatsDetailInternal(lines, *inactiveStatsMap, ifaceIndexNameMap);
     if (ret) {
         ALOGE("parse detail network stats failed: %s", strerror(errno));
         return ret;
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index ff62c0b..bf42b62 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -225,18 +225,11 @@
     ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID2, &result2, mFakeAppUidStatsMap));
     expectStatsEqual(value2, result2);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)2, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME3, TEST_UID2, TEST_COUNTERSET1, 0, lines.front());
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((unsigned long)3, lines.size());
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsInternal) {
@@ -297,24 +290,8 @@
                       mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)3, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)2, lines.size());
-    lines.clear();
-    ifaces.push_back(std::string(IFACE_NAME1));
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines.front());
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) {
@@ -333,24 +310,8 @@
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)3, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines.front());
-    lines.clear();
-    ifaces.push_back(std::string(IFACE_NAME1));
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)2, lines.size());
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestUnknownIfaceError) {
@@ -387,10 +348,8 @@
                                            ifname, curKey, &unknownIfaceBytesTotal));
     ASSERT_EQ(-1, unknownIfaceBytesTotal);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
     // TODO: find a way to test the total of unknown Iface Bytes go above limit.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
 }
@@ -458,18 +417,15 @@
     };
 
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
 
     // Test empty stats.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 0, lines.size());
     lines.clear();
 
     // Test 1 line stats.
     populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
     lines.clear();
@@ -480,8 +436,7 @@
     populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
                       mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 5, lines.size());
     lines.clear();
 
@@ -489,8 +444,7 @@
     populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
 
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 5, lines.size());
 
     // Verify Sorted & Grouped.
@@ -547,9 +501,7 @@
     // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
 
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 8, lines.size());
 
     // Uid 0 first
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
index 696a29a..6aa0fb4 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -101,6 +101,11 @@
 void NetworkTraceHandler::InitPerfettoTracing() {
   perfetto::TracingInitArgs args = {};
   args.backends |= perfetto::kSystemBackend;
+  // The following line disables the Perfetto system consumer. Perfetto inlines
+  // the call to `Initialize` which allows the compiler to see that the branch
+  // with the SystemConsumerTracingBackend is not used. With LTO enabled, this
+  // strips the Perfetto consumer code and reduces the size of this binary by
+  // around 270KB total. Be careful when changing this value.
   args.enable_system_consumer = false;
   perfetto::Tracing::Initialize(args);
   NetworkTraceHandler::RegisterDataSource();
@@ -136,10 +141,12 @@
 }
 
 void NetworkTraceHandler::OnStart(const StartArgs&) {
+  if (mIsTest) return;  // Don't touch non-hermetic bpf in test.
   mStarted = sPoller.Start(mPollMs);
 }
 
 void NetworkTraceHandler::OnStop(const StopArgs&) {
+  if (mIsTest) return;  // Don't touch non-hermetic bpf in test.
   if (mStarted) sPoller.Stop();
   mStarted = false;
 }
@@ -179,24 +186,25 @@
     bundle.bytes += pkt.length;
   }
 
-  // If state was cleared, emit a separate packet to indicate it. This uses the
-  // overall minTs so it is sorted before any packets that follow.
   NetworkTraceState* incr_state = ctx.GetIncrementalState();
-  if (!bundles.empty() && mInternLimit && incr_state->cleared) {
-    auto clear = ctx.NewTracePacket();
-    clear->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
-    clear->set_timestamp(minTs);
-    incr_state->cleared = false;
-  }
-
   for (const auto& kv : bundles) {
     const BundleKey& key = kv.first;
     const BundleDetails& details = kv.second;
 
     auto dst = ctx.NewTracePacket();
-    dst->set_sequence_flags(TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
     dst->set_timestamp(details.minTs);
 
+    // Incremental state is only used when interning. Set the flag based on
+    // whether state was cleared. Leave the flag empty in non-intern configs.
+    if (mInternLimit > 0) {
+      if (incr_state->cleared) {
+        dst->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
+        incr_state->cleared = false;
+      } else {
+        dst->set_sequence_flags(TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
+      }
+    }
+
     auto* event = FillWithInterning(incr_state, key, dst.get());
 
     int count = details.time_and_len.size();
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
index c9eb183..f2c1a86 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
@@ -33,14 +33,6 @@
 using ::perfetto::protos::TracePacket;
 using ::perfetto::protos::TrafficDirection;
 
-// This handler makes OnStart and OnStop a no-op so that tracing is not really
-// started on the device.
-class HandlerForTest : public NetworkTraceHandler {
- public:
-  void OnStart(const StartArgs&) override {}
-  void OnStop(const StopArgs&) override {}
-};
-
 class NetworkTraceHandlerTest : public testing::Test {
  protected:
   // Starts a tracing session with the handler under test.
@@ -52,7 +44,7 @@
 
     perfetto::DataSourceDescriptor dsd;
     dsd.set_name("test.network_packets");
-    HandlerForTest::Register(dsd);
+    NetworkTraceHandler::Register(dsd, /*isTest=*/true);
 
     perfetto::TraceConfig cfg;
     cfg.add_buffers()->set_size_kb(1024);
@@ -94,7 +86,7 @@
                            std::vector<TracePacket>* output,
                            NetworkPacketTraceConfig config = {}) {
     auto session = StartTracing(config);
-    HandlerForTest::Trace([&](HandlerForTest::TraceContext ctx) {
+    NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
       ctx.GetDataSourceLocked()->Write(input, ctx);
       ctx.Flush();
     });
@@ -136,6 +128,7 @@
   EXPECT_THAT(events[0].network_packet().ip_proto(), 6);
   EXPECT_THAT(events[0].network_packet().tcp_flags(), 1);
   EXPECT_THAT(events[0].network_packet().length(), 100);
+  EXPECT_THAT(events[0].has_sequence_flags(), false);
 }
 
 TEST_F(NetworkTraceHandlerTest, WriteDirectionAndPorts) {
@@ -356,7 +349,7 @@
 
   auto session = StartTracing(config);
 
-  HandlerForTest::Trace([&](HandlerForTest::TraceContext ctx) {
+  NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
     ctx.GetDataSourceLocked()->Write(inputs[0], ctx);
     ctx.GetDataSourceLocked()->Write(inputs[1], ctx);
     ctx.GetDataSourceLocked()->Write(inputs[2], ctx);
@@ -374,20 +367,28 @@
   ASSERT_EQ(events[0].interned_data().packet_context().size(), 1);
   EXPECT_EQ(events[0].interned_data().packet_context(0).iid(), 1);
   EXPECT_EQ(events[0].interned_data().packet_context(0).ctx().uid(), 123);
+  EXPECT_EQ(events[0].sequence_flags(),
+            TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
 
   // First time seen, emit new interned data, bundle uses iid instead of ctx.
   EXPECT_EQ(events[1].network_packet_bundle().iid(), 2);
   ASSERT_EQ(events[1].interned_data().packet_context().size(), 1);
   EXPECT_EQ(events[1].interned_data().packet_context(0).iid(), 2);
   EXPECT_EQ(events[1].interned_data().packet_context(0).ctx().uid(), 456);
+  EXPECT_EQ(events[1].sequence_flags(),
+            TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
 
   // Not enough room in intern table (limit 2), inline the context.
   EXPECT_EQ(events[2].network_packet_bundle().ctx().uid(), 789);
   EXPECT_EQ(events[2].interned_data().packet_context().size(), 0);
+  EXPECT_EQ(events[2].sequence_flags(),
+            TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
 
   // Second time seen, no need to re-emit interned data, only record iid.
   EXPECT_EQ(events[3].network_packet_bundle().iid(), 1);
   EXPECT_EQ(events[3].interned_data().packet_context().size(), 0);
+  EXPECT_EQ(events[3].sequence_flags(),
+            TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
 }
 
 }  // namespace bpf
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
index 3abb49a..3de9897 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
@@ -99,6 +99,15 @@
     ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
   }
 
+  // Make sure everything in the system has actually seen the 'false' we just
+  // wrote, things should now be well and truly disabled.
+  synchronizeKernelRCU();
+
+  // Drain remaining events from the ring buffer now that tracing is disabled.
+  // This prevents the next trace from seeing stale events and allows writing
+  // the last batch of events to Perfetto.
+  ConsumeAllLocked();
+
   mTaskRunner.reset();
   mRingBuffer.reset();
 
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
index 03a1a44..1ffa927 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -26,7 +26,7 @@
 // TODO: set this to a proper value based on the map size;
 constexpr int TAG_STATS_MAP_SOFT_LIMIT = 3;
 constexpr int UID_ALL = -1;
-constexpr int TAG_ALL = -1;
+//constexpr int TAG_ALL = -1;
 constexpr int TAG_NONE = 0;
 constexpr int SET_ALL = -1;
 constexpr int SET_DEFAULT = 0;
@@ -64,8 +64,7 @@
                              const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
 // For test only
 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
-                                       const std::vector<std::string>& limitIfaces, int limitTag,
-                                       int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+                                       const BpfMap<StatsKey, StatsValue>& statsMap,
                                        const BpfMap<uint32_t, IfaceValue>& ifaceMap);
 // For test only
 int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
@@ -113,9 +112,7 @@
 
 int bpfGetUidStats(uid_t uid, Stats* stats);
 int bpfGetIfaceStats(const char* iface, Stats* stats);
-int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
-                               const std::vector<std::string>& limitIfaces, int limitTag,
-                               int limitUid);
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines);
 
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
 void groupNetworkStats(std::vector<stats_line>* lines);
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
index 80871c6..bc10e68 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -49,7 +49,7 @@
 // loss). When state is cleared, the state object is replaced with a new default
 // constructed instance.
 struct NetworkTraceState {
-  bool cleared;
+  bool cleared = true;
   std::unordered_map<BundleKey, uint64_t, BundleHash, BundleEq> iids;
 };
 
@@ -70,6 +70,9 @@
   // Connects to the system Perfetto daemon and registers the trace handler.
   static void InitPerfettoTracing();
 
+  // When isTest is true, skip non-hermetic code.
+  NetworkTraceHandler(bool isTest = false) : mIsTest(isTest) {}
+
   // perfetto::DataSource overrides:
   void OnSetup(const SetupArgs& args) override;
   void OnStart(const StartArgs&) override;
@@ -92,6 +95,7 @@
 
   static internal::NetworkTracePoller sPoller;
   bool mStarted;
+  bool mIsTest;
 
   // Values from config, see proto for details.
   uint32_t mPollMs;
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index cbe6691..a658791 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -72,6 +72,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -1105,9 +1106,12 @@
                 final String serviceName = serviceInfo.getServiceInstanceName();
                 final NsdServiceInfo servInfo = new NsdServiceInfo(serviceName, serviceType);
                 final Network network = serviceInfo.getNetwork();
+                // In MdnsDiscoveryManagerEvent, the Network can be null which means it is a
+                // network for Tethering interface. In other words, the network == null means the
+                // network has netId = INetd.LOCAL_NET_ID.
                 setServiceNetworkForCallback(
                         servInfo,
-                        network == null ? NETID_UNSET : network.netId,
+                        network == null ? INetd.LOCAL_NET_ID : network.netId,
                         serviceInfo.getInterfaceIndex());
                 return servInfo;
             }
@@ -1155,22 +1159,7 @@
                                 Log.e(TAG, "Invalid attribute", e);
                             }
                         }
-                        final List<InetAddress> addresses = new ArrayList<>();
-                        for (String ipv4Address : serviceInfo.getIpv4Addresses()) {
-                            try {
-                                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
-                            } catch (IllegalArgumentException e) {
-                                Log.wtf(TAG, "Invalid ipv4 address", e);
-                            }
-                        }
-                        for (String ipv6Address : serviceInfo.getIpv6Addresses()) {
-                            try {
-                                addresses.add(InetAddresses.parseNumericAddress(ipv6Address));
-                            } catch (IllegalArgumentException e) {
-                                Log.wtf(TAG, "Invalid ipv6 address", e);
-                            }
-                        }
-
+                        final List<InetAddress> addresses = getInetAddresses(serviceInfo);
                         if (addresses.size() != 0) {
                             info.setHostAddresses(addresses);
                             clientInfo.onResolveServiceSucceeded(clientId, info);
@@ -1202,21 +1191,7 @@
                             }
                         }
 
-                        final List<InetAddress> addresses = new ArrayList<>();
-                        for (String ipv4Address : serviceInfo.getIpv4Addresses()) {
-                            try {
-                                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
-                            } catch (IllegalArgumentException e) {
-                                Log.wtf(TAG, "Invalid ipv4 address", e);
-                            }
-                        }
-                        for (String ipv6Address : serviceInfo.getIpv6Addresses()) {
-                            try {
-                                addresses.add(InetAddresses.parseNumericAddress(ipv6Address));
-                            } catch (IllegalArgumentException e) {
-                                Log.wtf(TAG, "Invalid ipv6 address", e);
-                            }
-                        }
+                        final List<InetAddress> addresses = getInetAddresses(serviceInfo);
                         info.setHostAddresses(addresses);
                         clientInfo.onServiceUpdated(clientId, info);
                         break;
@@ -1232,6 +1207,36 @@
        }
     }
 
+    @NonNull
+    private static List<InetAddress> getInetAddresses(@NonNull MdnsServiceInfo serviceInfo) {
+        final List<String> v4Addrs = serviceInfo.getIpv4Addresses();
+        final List<String> v6Addrs = serviceInfo.getIpv6Addresses();
+        final List<InetAddress> addresses = new ArrayList<>(v4Addrs.size() + v6Addrs.size());
+        for (String ipv4Address : v4Addrs) {
+            try {
+                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
+            } catch (IllegalArgumentException e) {
+                Log.wtf(TAG, "Invalid ipv4 address", e);
+            }
+        }
+        for (String ipv6Address : v6Addrs) {
+            try {
+                final InetAddress addr = InetAddresses.parseNumericAddress(ipv6Address);
+                if (addr.isLinkLocalAddress()) {
+                    final Inet6Address v6Addr = Inet6Address.getByAddress(
+                            null /* host */, addr.getAddress(),
+                            serviceInfo.getInterfaceIndex());
+                    addresses.add(v6Addr);
+                } else {
+                    addresses.add(addr);
+                }
+            } catch (IllegalArgumentException | UnknownHostException e) {
+                Log.wtf(TAG, "Invalid ipv6 address", e);
+            }
+        }
+        return addresses;
+    }
+
     private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
         switch (netId) {
             case NETID_UNSET:
diff --git a/service-t/src/com/android/server/connectivity/mdns/ISocketNetLinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/ISocketNetLinkMonitor.java
new file mode 100644
index 0000000..ef3928c
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/ISocketNetLinkMonitor.java
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+/**
+ * The interface for netlink monitor.
+ */
+public interface ISocketNetLinkMonitor {
+
+    /**
+     * Returns if the netlink monitor is supported or not. By default, it is not supported.
+     */
+    default boolean isSupported() {
+        return false;
+    }
+
+    /**
+     * Starts the monitor.
+     */
+    default void startMonitoring() {
+    }
+
+    /**
+     * Stops the monitor.
+     */
+    default void stopMonitoring() {
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 119c7a8..31627f8 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -78,7 +78,7 @@
         }
 
         mPacketReader = new MulticastPacketReader(networkInterface.getName(), mFileDescriptor,
-                new Handler(looper), packetReadBuffer);
+                new Handler(looper), packetReadBuffer, port);
         mPacketReader.start();
     }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 5254342..7af2231 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -70,7 +70,7 @@
         }
 
         @Override
-        public void onSocketCreated(@NonNull Network network,
+        public void onSocketCreated(@Nullable Network network,
                 @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
             // The socket may be already created by other request before, try to get the stored
             // ReadPacketHandler.
@@ -86,7 +86,7 @@
         }
 
         @Override
-        public void onInterfaceDestroyed(@NonNull Network network,
+        public void onInterfaceDestroyed(@Nullable Network network,
                 @NonNull MdnsInterfaceSocket socket) {
             mSocketPacketHandlers.remove(socket);
             mActiveNetworkSockets.remove(socket);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 1d9a24c..0952e88 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -16,30 +16,30 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
-import android.net.INetd;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TetheringManager;
 import android.net.TetheringManager.TetheringEventCallback;
 import android.os.Handler;
 import android.os.Looper;
-import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
-import com.android.net.module.util.ip.NetlinkMonitor;
-import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkMessage;
 import com.android.server.connectivity.mdns.util.MdnsLogger;
 
 import java.io.IOException;
@@ -71,11 +71,12 @@
     @NonNull private final Dependencies mDependencies;
     @NonNull private final NetworkCallback mNetworkCallback;
     @NonNull private final TetheringEventCallback mTetheringEventCallback;
-    @NonNull private final NetlinkMonitor mNetlinkMonitor;
+    @NonNull private final ISocketNetLinkMonitor mSocketNetlinkMonitor;
     private final ArrayMap<Network, SocketInfo> mNetworkSockets = new ArrayMap<>();
     private final ArrayMap<String, SocketInfo> mTetherInterfaceSockets = new ArrayMap<>();
     private final ArrayMap<Network, LinkProperties> mActiveNetworksLinkProperties =
             new ArrayMap<>();
+    private final ArrayMap<Network, int[]> mActiveNetworksTransports = new ArrayMap<>();
     private final ArrayMap<SocketCallback, Network> mCallbacksToRequestedNetworks =
             new ArrayMap<>();
     private final List<String> mLocalOnlyInterfaces = new ArrayList<>();
@@ -98,7 +99,14 @@
             @Override
             public void onLost(Network network) {
                 mActiveNetworksLinkProperties.remove(network);
-                removeSocket(network, null /* interfaceName */);
+                mActiveNetworksTransports.remove(network);
+                removeNetworkSocket(network);
+            }
+
+            @Override
+            public void onCapabilitiesChanged(@NonNull Network network,
+                    @NonNull NetworkCapabilities networkCapabilities) {
+                mActiveNetworksTransports.put(network, networkCapabilities.getTransportTypes());
             }
 
             @Override
@@ -118,7 +126,8 @@
             }
         };
 
-        mNetlinkMonitor = new SocketNetlinkMonitor(mHandler);
+        mSocketNetlinkMonitor = SocketNetLinkMonitorFactory.createNetLinkMonitor(mHandler,
+                LOGGER.mLog);
     }
 
     /**
@@ -133,11 +142,6 @@
             return ni == null ? null : new NetworkInterfaceWrapper(ni);
         }
 
-        /*** Check whether given network interface can support mdns */
-        public boolean canScanOnInterface(@NonNull NetworkInterfaceWrapper networkInterface) {
-            return MulticastNetworkInterfaceProvider.canScanOnInterface(networkInterface);
-        }
-
         /*** Create a MdnsInterfaceSocket */
         public MdnsInterfaceSocket createMdnsInterfaceSocket(
                 @NonNull NetworkInterface networkInterface, int port, @NonNull Looper looper,
@@ -157,18 +161,6 @@
         }
     }
 
-    private static class SocketNetlinkMonitor extends NetlinkMonitor {
-        SocketNetlinkMonitor(Handler handler) {
-            super(handler, LOGGER.mLog, TAG, OsConstants.NETLINK_ROUTE,
-                    NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
-        }
-
-        @Override
-        public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
-            // TODO: Handle netlink message.
-        }
-    }
-
     /*** Ensure that current running thread is same as given handler thread */
     public static void ensureRunningOnHandlerThread(Handler handler) {
         if (handler.getLooper().getThread() != Thread.currentThread()) {
@@ -193,7 +185,9 @@
         final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
         tetheringManager.registerTetheringEventCallback(mHandler::post, mTetheringEventCallback);
 
-        mHandler.post(mNetlinkMonitor::start);
+        if (mSocketNetlinkMonitor.isSupported()) {
+            mHandler.post(mSocketNetlinkMonitor::startMonitoring);
+        }
         mMonitoringSockets = true;
     }
 
@@ -210,7 +204,9 @@
                     TetheringManager.class);
             tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
 
-            mHandler.post(mNetlinkMonitor::stop);
+            if (mSocketNetlinkMonitor.isSupported()) {
+                mHandler.post(mSocketNetlinkMonitor::stopMonitoring);
+            }
             // Clear all saved status.
             mActiveNetworksLinkProperties.clear();
             mNetworkSockets.clear();
@@ -235,7 +231,7 @@
 
     /*** Check whether the target network is matched current network */
     public static boolean isNetworkMatched(@Nullable Network targetNetwork,
-            @NonNull Network currentNetwork) {
+            @Nullable Network currentNetwork) {
         return targetNetwork == null || targetNetwork.equals(currentNetwork);
     }
 
@@ -258,9 +254,10 @@
             return;
         }
 
+        final NetworkAsKey networkKey = new NetworkAsKey(network);
         final SocketInfo socketInfo = mNetworkSockets.get(network);
         if (socketInfo == null) {
-            createSocket(network, lp);
+            createSocket(networkKey, lp);
         } else {
             // Update the addresses of this socket.
             final List<LinkAddress> addresses = lp.getLinkAddresses();
@@ -295,16 +292,16 @@
         final CompareResult<String> interfaceDiff = new CompareResult<>(
                 current, updated);
         for (String name : interfaceDiff.added) {
-            createSocket(new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(name));
+            createSocket(LOCAL_NET, createLPForTetheredInterface(name));
         }
         for (String name : interfaceDiff.removed) {
-            removeSocket(new Network(INetd.LOCAL_NET_ID), name);
+            removeTetherInterfaceSocket(name);
         }
         current.clear();
         current.addAll(updated);
     }
 
-    private void createSocket(Network network, LinkProperties lp) {
+    private void createSocket(NetworkKey networkKey, LinkProperties lp) {
         final String interfaceName = lp.getInterfaceName();
         if (interfaceName == null) {
             Log.e(TAG, "Can not create socket with null interface name.");
@@ -314,46 +311,97 @@
         try {
             final NetworkInterfaceWrapper networkInterface =
                     mDependencies.getNetworkInterfaceByName(interfaceName);
-            if (networkInterface == null || !mDependencies.canScanOnInterface(networkInterface)) {
+            // There are no transports for tethered interfaces. Other interfaces should always
+            // have transports since LinkProperties updates are always sent after
+            // NetworkCapabilities updates.
+            final int[] transports;
+            if (networkKey == LOCAL_NET) {
+                transports = new int[0];
+            } else {
+                transports = mActiveNetworksTransports.getOrDefault(
+                        ((NetworkAsKey) networkKey).mNetwork, new int[0]);
+            }
+            if (networkInterface == null || !isMdnsCapableInterface(networkInterface, transports)) {
                 return;
             }
 
             if (DBG) {
-                Log.d(TAG, "Create a socket on network:" + network
+                Log.d(TAG, "Create a socket on network:" + networkKey
                         + " with interfaceName:" + interfaceName);
             }
             final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
                     networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper,
                     mPacketReadBuffer);
             final List<LinkAddress> addresses;
-            if (network.netId == INetd.LOCAL_NET_ID) {
+            if (networkKey == LOCAL_NET) {
                 addresses = CollectionUtils.map(
-                        networkInterface.getInterfaceAddresses(), LinkAddress::new);
+                        networkInterface.getInterfaceAddresses(),
+                        i -> new LinkAddress(i.getAddress(), i.getNetworkPrefixLength()));
                 mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
             } else {
                 addresses = lp.getLinkAddresses();
-                mNetworkSockets.put(network, new SocketInfo(socket, addresses));
+                mNetworkSockets.put(((NetworkAsKey) networkKey).mNetwork,
+                        new SocketInfo(socket, addresses));
             }
             // Try to join IPv4/IPv6 group.
             socket.joinGroup(addresses);
 
             // Notify the listeners which need this socket.
-            notifySocketCreated(network, socket, addresses);
+            if (networkKey == LOCAL_NET) {
+                notifySocketCreated(null /* network */, socket, addresses);
+            } else {
+                notifySocketCreated(((NetworkAsKey) networkKey).mNetwork, socket, addresses);
+            }
         } catch (IOException e) {
             Log.e(TAG, "Create a socket failed with interface=" + interfaceName, e);
         }
     }
 
-    private void removeSocket(Network network, String interfaceName) {
-        final SocketInfo socketInfo = network.netId == INetd.LOCAL_NET_ID
-                ? mTetherInterfaceSockets.remove(interfaceName)
-                : mNetworkSockets.remove(network);
+    private boolean isMdnsCapableInterface(
+            @NonNull NetworkInterfaceWrapper iface, @NonNull int[] transports) {
+        try {
+            // Never try mDNS on cellular, or on interfaces with incompatible flags
+            if (CollectionUtils.contains(transports, TRANSPORT_CELLULAR)
+                    || iface.isLoopback()
+                    || iface.isPointToPoint()
+                    || iface.isVirtual()
+                    || !iface.isUp()) {
+                return false;
+            }
+
+            // Otherwise, always try mDNS on non-VPN Wifi.
+            if (!CollectionUtils.contains(transports, TRANSPORT_VPN)
+                    && CollectionUtils.contains(transports, TRANSPORT_WIFI)) {
+                return true;
+            }
+
+            // For other transports, or no transports (tethering downstreams), do mDNS based on the
+            // interface flags. This is not always reliable (for example some Wifi interfaces may
+            // not have the MULTICAST flag even though they can do mDNS, and some cellular
+            // interfaces may have the BROADCAST or MULTICAST flags), so checks are done based on
+            // transports above in priority.
+            return iface.supportsMulticast();
+        } catch (SocketException e) {
+            Log.e(TAG, "Error checking interface flags", e);
+            return false;
+        }
+    }
+
+    private void removeNetworkSocket(Network network) {
+        final SocketInfo socketInfo = mNetworkSockets.remove(network);
         if (socketInfo == null) return;
 
         socketInfo.mSocket.destroy();
         notifyInterfaceDestroyed(network, socketInfo.mSocket);
     }
 
+    private void removeTetherInterfaceSocket(String interfaceName) {
+        final SocketInfo socketInfo = mTetherInterfaceSockets.remove(interfaceName);
+        if (socketInfo == null) return;
+        socketInfo.mSocket.destroy();
+        notifyInterfaceDestroyed(null /* network */, socketInfo.mSocket);
+    }
+
     private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
             List<LinkAddress> addresses) {
         for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
@@ -393,7 +441,7 @@
                 if (DBG) Log.d(TAG, "There is no LinkProperties for this network:" + network);
                 return;
             }
-            createSocket(network, lp);
+            createSocket(new NetworkAsKey(network), lp);
         } else {
             // Notify the socket for requested network.
             cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
@@ -403,12 +451,11 @@
     private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
         final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
         if (socketInfo == null) {
-            createSocket(
-                    new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(interfaceName));
+            createSocket(LOCAL_NET, createLPForTetheredInterface(interfaceName));
         } else {
             // Notify the socket for requested network.
             cb.onSocketCreated(
-                    new Network(INetd.LOCAL_NET_ID), socketInfo.mSocket, socketInfo.mAddresses);
+                    null /* network */, socketInfo.mSocket, socketInfo.mAddresses);
         }
     }
 
@@ -467,7 +514,7 @@
             final SocketInfo info = mTetherInterfaceSockets.valueAt(i);
             info.mSocket.destroy();
             // Still notify to unrequester for socket destroy.
-            cb.onInterfaceDestroyed(new Network(INetd.LOCAL_NET_ID), info.mSocket);
+            cb.onInterfaceDestroyed(null /* network */, info.mSocket);
         }
         mTetherInterfaceSockets.clear();
 
@@ -478,13 +525,49 @@
     /*** Callbacks for listening socket changes */
     public interface SocketCallback {
         /*** Notify the socket is created */
-        default void onSocketCreated(@NonNull Network network, @NonNull MdnsInterfaceSocket socket,
+        default void onSocketCreated(@Nullable Network network, @NonNull MdnsInterfaceSocket socket,
                 @NonNull List<LinkAddress> addresses) {}
         /*** Notify the interface is destroyed */
-        default void onInterfaceDestroyed(@NonNull Network network,
+        default void onInterfaceDestroyed(@Nullable Network network,
                 @NonNull MdnsInterfaceSocket socket) {}
         /*** Notify the addresses is changed on the network */
-        default void onAddressesChanged(@NonNull Network network,
+        default void onAddressesChanged(@Nullable Network network,
                 @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {}
     }
+
+    private interface NetworkKey {
+    }
+
+    private static final NetworkKey LOCAL_NET = new NetworkKey() {
+        @Override
+        public String toString() {
+            return "NetworkKey:LOCAL_NET";
+        }
+    };
+
+    private static class NetworkAsKey implements NetworkKey {
+        private final Network mNetwork;
+
+        NetworkAsKey(Network network) {
+            this.mNetwork = network;
+        }
+
+        @Override
+        public int hashCode() {
+            return mNetwork.hashCode();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof NetworkAsKey)) {
+                return false;
+            }
+            return mNetwork.equals(((NetworkAsKey) other).mNetwork);
+        }
+
+        @Override
+        public String toString() {
+            return "NetworkAsKey{ network=" + mNetwork + " }";
+        }
+    }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
index ade7b95..f248c98 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
@@ -148,7 +148,7 @@
     }
 
     /*** Check whether given network interface can support mdns */
-    public static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
+    private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
         try {
             if ((networkInterface == null)
                     || networkInterface.isLoopback()
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
index b597f0a..078c4dd 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
@@ -59,11 +59,12 @@
      * Create a new {@link MulticastPacketReader}.
      * @param socket Socket to read from. This will *not* be closed when the reader terminates.
      * @param buffer Buffer to read packets into. Will only be used from the handler thread.
+     * @param port the port number for the socket
      */
     protected MulticastPacketReader(@NonNull String interfaceTag,
             @NonNull ParcelFileDescriptor socket, @NonNull Handler handler,
-            @NonNull byte[] buffer) {
-        super(handler, new RecvBuffer(buffer, new InetSocketAddress()));
+            @NonNull byte[] buffer, int port) {
+        super(handler, new RecvBuffer(buffer, new InetSocketAddress(port)));
         mLogTag = MulticastPacketReader.class.getSimpleName() + "/" + interfaceTag;
         mSocket = socket;
         mHandler = handler;
diff --git a/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java b/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
new file mode 100644
index 0000000..8f6aecc
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.internal.SocketNetlinkMonitor;
+
+/**
+ * The factory class for creating the netlink monitor.
+ */
+public class SocketNetLinkMonitorFactory {
+
+    /**
+     * Creates a new netlink monitor.
+     */
+    public static ISocketNetLinkMonitor createNetLinkMonitor(@NonNull final Handler handler,
+            @NonNull SharedLog log) {
+        return new SocketNetlinkMonitor(handler, log);
+    }
+
+    private SocketNetLinkMonitorFactory() {
+    }
+
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
new file mode 100644
index 0000000..e053413
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.server.connectivity.mdns.internal;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.system.OsConstants;
+
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.NetlinkMonitor;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.server.connectivity.mdns.ISocketNetLinkMonitor;
+
+/**
+ * The netlink monitor for MdnsSocketProvider.
+ */
+public class SocketNetlinkMonitor extends NetlinkMonitor implements ISocketNetLinkMonitor {
+
+    public SocketNetlinkMonitor(@NonNull final Handler handler, @NonNull SharedLog log) {
+        super(handler, log, SocketNetlinkMonitor.class.getSimpleName(), OsConstants.NETLINK_ROUTE,
+                NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
+    }
+
+    @Override
+    public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
+
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public void startMonitoring() {
+        this.start();
+    }
+
+    @Override
+    public void stopMonitoring() {
+        this.stop();
+    }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 5952eae..5f66f47 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -84,12 +84,9 @@
          * are expected to monotonically increase since device boot.
          */
         @NonNull
-        public NetworkStats getNetworkStatsDetail(int limitUid, @Nullable String[] limitIfaces,
-                int limitTag) throws IOException {
+        public NetworkStats getNetworkStatsDetail() throws IOException {
             final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-            // TODO: remove both path and useBpfStats arguments.
-            // The path is never used if useBpfStats is true.
-            final int ret = nativeReadNetworkStatsDetail(stats, limitUid, limitIfaces, limitTag);
+            final int ret = nativeReadNetworkStatsDetail(stats);
             if (ret != 0) {
                 throw new IOException("Failed to parse network stats");
             }
@@ -213,8 +210,7 @@
             requestSwapActiveStatsMapLocked();
             // Stats are always read from the inactive map, so they must be read after the
             // swap
-            final NetworkStats stats = mDeps.getNetworkStatsDetail(
-                    UID_ALL, INTERFACES_ALL, TAG_ALL);
+            final NetworkStats stats = mDeps.getNetworkStatsDetail();
             // BPF stats are incremental; fold into mPersistSnapshot.
             mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
             mPersistSnapshot.combineAllValues(stats);
@@ -301,8 +297,7 @@
      * are expected to monotonically increase since device boot.
      */
     @VisibleForTesting
-    public static native int nativeReadNetworkStatsDetail(NetworkStats stats, int limitUid,
-            String[] limitIfaces, int limitTag);
+    public static native int nativeReadNetworkStatsDetail(NetworkStats stats);
 
     @VisibleForTesting
     public static native int nativeReadNetworkStatsDev(NetworkStats stats);
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index ad4596d..19f2fb8 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -26,9 +26,14 @@
 #include <nativehelper/JNIHelp.h>
 #include <net/if.h>
 #include <spawn.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/xattr.h>
 #include <string>
+#include <unistd.h>
 
+#include <android-modules-utils/sdk_level.h>
 #include <bpf/BpfMap.h>
 #include <bpf/BpfUtils.h>
 #include <netjniutils/netjniutils.h>
@@ -45,7 +50,97 @@
 #define DEVICEPREFIX "v4-"
 
 namespace android {
-static const char* kClatdPath = "/apex/com.android.tethering/bin/for-system/clatd";
+
+#define ALOGF(s ...) do { ALOGE(s); abort(); } while(0)
+
+enum verify { VERIFY_DIR, VERIFY_BIN, VERIFY_PROG, VERIFY_MAP_RO, VERIFY_MAP_RW };
+
+static void verifyPerms(const char * const path,
+                        const mode_t mode, const uid_t uid, const gid_t gid,
+                        const char * const ctxt,
+                        const verify vtype) {
+    struct stat s = {};
+
+    if (lstat(path, &s)) ALOGF("lstat '%s' errno=%d", path, errno);
+    if (s.st_mode != mode) ALOGF("'%s' mode is 0%o != 0%o", path, s.st_mode, mode);
+    if (s.st_uid != uid) ALOGF("'%s' uid is %d != %d", path, s.st_uid, uid);
+    if (s.st_gid != gid) ALOGF("'%s' gid is %d != %d", path, s.st_gid, gid);
+
+    char b[255] = {};
+    int v = lgetxattr(path, "security.selinux", &b, sizeof(b));
+    if (v < 0) ALOGF("lgetxattr '%s' errno=%d", path, errno);
+    if (strncmp(ctxt, b, sizeof(b))) ALOGF("context of '%s' is '%s' != '%s'", path, b, ctxt);
+
+    int fd = -1;
+
+    switch (vtype) {
+      case VERIFY_DIR: return;
+      case VERIFY_BIN: return;
+      case VERIFY_PROG:   fd = bpf::retrieveProgram(path); break;
+      case VERIFY_MAP_RO: fd = bpf::mapRetrieveRO(path); break;
+      case VERIFY_MAP_RW: fd = bpf::mapRetrieveRW(path); break;
+    }
+
+    if (fd < 0) ALOGF("bpf_obj_get '%s' failed, errno=%d", path, errno);
+
+    if (fd >= 0) close(fd);
+}
+
+#undef ALOGF
+
+bool isGsiImage() {
+    // this implementation matches 2 other places in the codebase (same function name too)
+    return !access("/system/system_ext/etc/init/init.gsi.rc", F_OK);
+}
+
+static const char* kClatdDir = "/apex/com.android.tethering/bin/for-system";
+static const char* kClatdBin = "/apex/com.android.tethering/bin/for-system/clatd";
+
+#define V(path, md, uid, gid, ctx, vtype) \
+    verifyPerms((path), (md), AID_ ## uid, AID_ ## gid, "u:object_r:" ctx ":s0", VERIFY_ ## vtype)
+
+static void verifyClatPerms() {
+    // We might run as part of tests instead of as part of system server
+    if (getuid() != AID_SYSTEM) return;
+
+    // First verify the clatd directory and binary,
+    // since this is built into the apex file system image,
+    // failures here are 99% likely to be build problems.
+    V(kClatdDir, S_IFDIR|0750, ROOT, SYSTEM, "system_file", DIR);
+    V(kClatdBin, S_IFREG|S_ISUID|S_ISGID|0755, CLAT, CLAT, "clatd_exec", BIN);
+
+    // Move on to verifying that the bpf programs and maps are as expected.
+    // This relies on the kernel and bpfloader.
+
+    // Clat BPF was only mainlined during T.
+    if (!modules::sdklevel::IsAtLeastT()) return;
+
+    // HACK: some old vendor kernels lack ~5.10 backport of 'bpffs selinux genfscon' support.
+    // This is *NOT* supported, but let's allow, at least for now, U+ GSI to boot on them.
+    // (without this hack pixel5 R vendor + U gsi breaks)
+    if (isGsiImage() && !bpf::isAtLeastKernelVersion(5, 10, 0)) return;
+
+    V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
+    V("/sys/fs/bpf/net_shared", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
+
+    // pre-U we do not have selinux privs to getattr on bpf maps/progs
+    // so while the below *should* be as listed, we have no way to actually verify
+    if (!modules::sdklevel::IsAtLeastU()) return;
+
+#define V2(path, md, vtype) \
+    V("/sys/fs/bpf/net_shared/" path, (md), ROOT, SYSTEM, "fs_bpf_net_shared", vtype)
+
+    V2("prog_clatd_schedcls_egress4_clat_rawip",  S_IFREG|0440, PROG);
+    V2("prog_clatd_schedcls_ingress6_clat_rawip", S_IFREG|0440, PROG);
+    V2("prog_clatd_schedcls_ingress6_clat_ether", S_IFREG|0440, PROG);
+    V2("map_clatd_clat_egress4_map",              S_IFREG|0660, MAP_RW);
+    V2("map_clatd_clat_ingress6_map",             S_IFREG|0660, MAP_RW);
+
+#undef V2
+
+}
+
+#undef V
 
 static void throwIOException(JNIEnv* env, const char* msg, int error) {
     jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
@@ -365,7 +460,7 @@
 
     // 5. actually perform vfork/dup2/execve
     pid_t pid;
-    if (int ret = posix_spawn(&pid, kClatdPath, &fa, &attr, (char* const*)args, nullptr)) {
+    if (int ret = posix_spawn(&pid, kClatdBin, &fa, &attr, (char* const*)args, nullptr)) {
         posix_spawnattr_destroy(&attr);
         posix_spawn_file_actions_destroy(&fa);
         throwIOException(env, "posix_spawn failed", ret);
@@ -405,7 +500,9 @@
     if (ret == 0) {
         ALOGE("Failed to SIGTERM clatd pid=%d, try SIGKILL", pid);
         // TODO: fix that kill failed or waitpid doesn't return.
-        kill(pid, SIGKILL);
+        if (kill(pid, SIGKILL)) {
+            ALOGE("Failed to SIGKILL clatd pid=%d: %s", pid, strerror(errno));
+        }
         ret = waitpid(pid, &status, 0);
     }
     if (ret == -1) {
@@ -484,6 +581,7 @@
 };
 
 int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+    verifyClatPerms();
     return jniRegisterNativeMethods(env,
             "android/net/connectivity/com/android/server/connectivity/ClatCoordinator",
             gMethods, NELEM(gMethods));
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 7e288c6..acce95d 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -52,6 +52,7 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructTimeval;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -152,6 +153,9 @@
     // TODO: Remove this when TCP polling design is replaced with callback.
     private long mTestLowTcpPollingTimerUntilMs = 0;
 
+    private static final int MAX_EVENTS_LOGS = 40;
+    private final LocalLog mEventLog = new LocalLog(MAX_EVENTS_LOGS);
+
     /**
      * Information about a managed keepalive.
      *
@@ -230,6 +234,7 @@
 
         @Override
         public void binderDied() {
+            mEventLog.log("Binder died : " + mCallback);
             mConnectivityServiceHandler.post(() -> cleanupAutoOnOffKeepalive(this));
         }
 
@@ -332,6 +337,7 @@
      * @param autoKi the keepalive to resume
      */
     public void handleMaybeResumeKeepalive(@NonNull AutomaticOnOffKeepalive autoKi) {
+        mEventLog.log("Resume keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
         // Might happen if the automatic keepalive was removed by the app just as the alarm fires.
         if (!mAutomaticOnOffKeepalives.contains(autoKi)) return;
         if (STATE_ALWAYS_ON == autoKi.mAutomaticOnOffState) {
@@ -377,6 +383,7 @@
      * Handle stop all keepalives on the specific network.
      */
     public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
+        mEventLog.log("Stop all keepalives on " + nai.network + " because " + reason);
         mKeepaliveTracker.handleStopAllKeepalives(nai, reason);
         final List<AutomaticOnOffKeepalive> matches =
                 CollectionUtils.filter(mAutomaticOnOffKeepalives, it -> it.mKi.getNai() == nai);
@@ -392,6 +399,7 @@
      */
     public void handleStartKeepalive(Message message) {
         final AutomaticOnOffKeepalive autoKi = (AutomaticOnOffKeepalive) message.obj;
+        mEventLog.log("Start keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
         mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
 
         // Add automatic on/off request into list to track its life cycle.
@@ -410,9 +418,11 @@
 
     private void handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
         mKeepaliveTracker.handleStartKeepalive(ki);
+        mEventLog.log("Resumed successfully keepalive " + ki.mCallback + " on " + ki.mNai);
     }
 
     private void handlePauseKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+        mEventLog.log("Suspend keepalive " + ki.mCallback + " on " + ki.mNai);
         // TODO : mKT.handleStopKeepalive should take a KeepaliveInfo instead
         mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), SUCCESS_PAUSED);
     }
@@ -421,6 +431,7 @@
      * Handle stop keepalives on the specific network with given slot.
      */
     public void handleStopKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi, int reason) {
+        mEventLog.log("Stop keepalive " + autoKi.mCallback + " because " + reason);
         // Stop the keepalive unless it was suspended. This includes the case where it's managed
         // but enabled, and the case where it's always on.
         if (autoKi.mAutomaticOnOffState != STATE_SUSPENDED) {
@@ -466,6 +477,11 @@
         try {
             final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
                     automaticOnOffKeepalives, underpinnedNetwork);
+            mEventLog.log("Start natt keepalive " + cb + " on " + nai.network
+                    + " " + srcAddrString + ":" + srcPort
+                    + " → " + dstAddrString + ":" + dstPort
+                    + " auto=" + autoKi
+                    + " underpinned=" + underpinnedNetwork);
             mConnectivityServiceHandler.obtainMessage(NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
                     // TODO : move ConnectivityService#encodeBool to a static lib.
                     automaticOnOffKeepalives ? 1 : 0, 0, autoKi).sendToTarget();
@@ -496,6 +512,11 @@
         try {
             final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
                     automaticOnOffKeepalives, underpinnedNetwork);
+            mEventLog.log("Start natt keepalive " + cb + " on " + nai.network
+                    + " " + srcAddrString
+                    + " → " + dstAddrString + ":" + dstPort
+                    + " auto=" + autoKi
+                    + " underpinned=" + underpinnedNetwork);
             mConnectivityServiceHandler.obtainMessage(NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
                     // TODO : move ConnectivityService#encodeBool to a static lib.
                     automaticOnOffKeepalives ? 1 : 0, 0, autoKi).sendToTarget();
@@ -550,6 +571,11 @@
             pw.println(autoKi.toString());
         }
         pw.decreaseIndent();
+
+        pw.println("Events (most recent first):");
+        pw.increaseIndent();
+        mEventLog.reverseDump(pw);
+        pw.decreaseIndent();
     }
 
     /**
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 45da0ea..cdc0aa9 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -22,6 +22,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.annotation.NonNull;
+import android.app.ActivityOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -33,6 +34,8 @@
 import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiInfo;
+import android.os.Build;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -45,6 +48,7 @@
 import com.android.connectivity.resources.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.modules.utils.build.SdkLevel;
 
 public class NetworkNotificationManager {
 
@@ -328,7 +332,26 @@
         }
 
         try {
-            intent.send();
+            Bundle options = null;
+
+            if (SdkLevel.isAtLeastU() && intent.isActivity()) {
+                // Also check SDK_INT >= T separately, as the linter in some T-based branches does
+                // not recognize "isAtLeastU && something" as an SDK check for T+ APIs.
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                    // Android U requires pending intent background start mode to be specified:
+                    // See #background-activity-restrictions in
+                    // https://developer.android.com/about/versions/14/behavior-changes-14
+                    // But setPendingIntentBackgroundActivityStartMode is U+, and replaces
+                    // setPendingIntentBackgroundActivityLaunchAllowed which is T+ but deprecated.
+                    // Use setPendingIntentBackgroundActivityLaunchAllowed as the U+ version is not
+                    // yet available in all branches.
+                    final ActivityOptions activityOptions = ActivityOptions.makeBasic();
+                    activityOptions.setPendingIntentBackgroundActivityLaunchAllowed(true);
+                    options = activityOptions.toBundle();
+                }
+            }
+
+            intent.send(null, 0, null, null, null, null, options);
         } catch (PendingIntent.CanceledException e) {
             Log.e(TAG, "Error sending dialog PendingIntent", e);
         }
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 06af3c0..aae3425 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -1387,6 +1387,9 @@
             // If the test network is restricted, then the network may declare any transport, and
             // appended with TRANSPORT_TEST.
             expectedNcBuilder.addTransportType(TRANSPORT_CELLULAR);
+        } else {
+            // If the test network only has TRANSPORT_TEST, then it can keep the subscription IDs.
+            expectedNcBuilder.setSubscriptionIds(Set.of(TEST_SUBID1));
         }
         expectedNcBuilder.addTransportType(TRANSPORT_TEST);
 
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
index fb6759e..fd7bd74 100644
--- a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -30,16 +30,17 @@
 import android.net.NetworkTemplate.MATCH_WIFI
 import android.net.NetworkTemplate.NETWORK_TYPE_ALL
 import android.net.NetworkTemplate.OEM_MANAGED_ALL
+import android.os.Build
 import android.telephony.TelephonyManager
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.SC_V2
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
 
 private const val TEST_IMSI1 = "imsi"
 private const val TEST_WIFI_KEY1 = "wifiKey1"
@@ -96,9 +97,27 @@
         }
 
         // Verify carrier and mobile template cannot contain one of subscriber Id is null.
-        listOf(MATCH_MOBILE, MATCH_CARRIER).forEach {
+        assertFailsWith<IllegalArgumentException> {
+            NetworkTemplate.Builder(MATCH_CARRIER).setSubscriberIds(setOf(null)).build()
+        }
+        val firstSdk = Build.VERSION.DEVICE_INITIAL_SDK_INT
+        if (firstSdk > Build.VERSION_CODES.TIRAMISU) {
             assertFailsWith<IllegalArgumentException> {
-                NetworkTemplate.Builder(it).setSubscriberIds(setOf(null)).build()
+                NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(null)).build()
+            }
+        } else {
+            NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(null)).build().let {
+                val expectedTemplate = NetworkTemplate(
+                    MATCH_MOBILE,
+                    arrayOfNulls<String>(1) /*subscriberIds*/,
+                    emptyArray<String>() /*wifiNetworkKey*/,
+                    METERED_ALL,
+                    ROAMING_ALL,
+                    DEFAULT_NETWORK_ALL,
+                    NETWORK_TYPE_ALL,
+                    OEM_MANAGED_ALL
+                )
+                assertEquals(expectedTemplate, it)
             }
         }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index b6902b5..c28ee64 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -1274,6 +1274,31 @@
     }
 
     @Test
+    public void testSocketClosed() throws Exception {
+        assumeTrue(supportedHardware());
+
+        final FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
+        final List<FileDescriptor> remoteFds = new ArrayList<>();
+
+        for (int i = 0; i < 30; i++) {
+            remoteFds.add(openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS));
+        }
+
+        final String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"192.0.2.0/24", "2001:db8::/32"},
+                allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+        // Socket owned by VPN uid is not closed
+        assertSocketStillOpen(localFd, TEST_HOST);
+
+        // Sockets not owned by VPN uid are closed
+        for (final FileDescriptor remoteFd: remoteFds) {
+            assertSocketClosed(remoteFd, TEST_HOST);
+        }
+    }
+
+    @Test
     public void testExcludedRoutes() throws Exception {
         assumeTrue(supportedHardware());
         assumeTrue(SdkLevel.isAtLeastT());
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 603779d..3ca4775 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -51,6 +51,10 @@
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
     }
 
+    public void testSocketClosed() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSocketClosed");
+    }
+
     public void testGetConnectionOwnerUidSecurity() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
     }
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 774176f..d6d384b 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2409,6 +2409,7 @@
         }
     }
 
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
     public void testBlockedStatusCallback() throws Exception {
         // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
@@ -2555,15 +2556,14 @@
         try {
             tetherEventCallback.assumeWifiTetheringSupported(mContext);
 
-            final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
-            mCtsNetUtils.ensureWifiConnected();
-            registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
+            tetherUtils.startWifiTethering(tetherEventCallback);
             // Update setting to verify the behavior.
             setAirplaneMode(true);
-            // Verify wifi lost to make sure airplane mode takes effect. This could
+            // Verify softap lost to make sure airplane mode takes effect. This could
             // prevent the race condition between airplane mode enabled and the followed
             // up wifi tethering enabled.
-            waitForLost(wifiCb);
+            tetherEventCallback.expectNoTetheringActive();
+
             // start wifi tethering
             tetherUtils.startWifiTethering(tetherEventCallback);
 
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 4c63cb0..db7f38c 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -96,6 +96,8 @@
 import com.android.testutils.waitForIdle
 import java.io.File
 import java.io.IOException
+import java.net.Inet6Address
+import java.net.InetAddress
 import java.net.NetworkInterface
 import java.net.ServerSocket
 import java.nio.charset.StandardCharsets
@@ -132,6 +134,7 @@
 
 @AppModeFull(reason = "Socket cannot bind in instant app mode")
 @RunWith(AndroidJUnit4::class)
+@ConnectivityModuleTest
 class NsdManagerTest {
     // Rule used to filter CtsNetTestCasesMaxTargetSdkXX
     @get:Rule
@@ -400,6 +403,13 @@
         // Wait until the link-local address can be used. Address flags are not available without
         // elevated permissions, so check that bindSocket works.
         PollingCheck.check("No usable v6 address on interface after $TIMEOUT_MS ms", TIMEOUT_MS) {
+            // To avoid race condition between socket connection succeeding and interface returning
+            // a non-empty address list. Verify that interface returns a non-empty list, before
+            // trying the socket connection.
+            if (NetworkInterface.getByName(ifaceName).interfaceAddresses.isEmpty()) {
+                return@check false
+            }
+
             val sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
             tryTest {
                 network.bindSocket(sock)
@@ -697,6 +707,20 @@
         }
     }
 
+    private fun checkAddressScopeId(iface: TestNetworkInterface, address: List<InetAddress>) {
+        val targetSdkVersion = context.packageManager
+            .getTargetSdkVersion(context.applicationInfo.packageName)
+        if (targetSdkVersion <= Build.VERSION_CODES.TIRAMISU) {
+            return
+        }
+        val ifaceIdx = NetworkInterface.getByName(iface.interfaceName).index
+        address.forEach {
+            if (it is Inet6Address && it.isLinkLocalAddress) {
+                assertEquals(ifaceIdx, it.scopeId)
+            }
+        }
+    }
+
     @Test
     fun testNsdManager_ResolveOnNetwork() {
         // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
@@ -732,6 +756,7 @@
                 assertEquals(registeredInfo.serviceName, it.serviceName)
                 assertEquals(si.port, it.port)
                 assertEquals(testNetwork1.network, nsdShim.getNetwork(it))
+                checkAddressScopeId(testNetwork1.iface, it.hostAddresses)
             }
             // TODO: check that MDNS packets are sent only on testNetwork1.
         } cleanupStep {
@@ -971,6 +996,7 @@
             for (hostAddress in hostAddresses) {
                 assertTrue(addresses.contains(hostAddress))
             }
+            checkAddressScopeId(testNetwork1.iface, serviceInfoCb.serviceInfo.hostAddresses)
         } cleanupStep {
             nsdManager.unregisterService(registrationRecord)
             registrationRecord.expectCallback<ServiceUnregistered>()
diff --git a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index cbe54f8..1a780a7 100644
--- a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -66,7 +66,6 @@
         InetAddress[] addresses;
         try {
             addresses = InetAddress.getAllByName(TEST_HOST);
-            mTestHostAddress = addresses[0];
         } catch (UnknownHostException uhe) {
             throw new AssertionError(
                 "Unable to test SSLCertificateSocketFactory: cannot resolve " + TEST_HOST, uhe);
@@ -76,10 +75,11 @@
             .map(addr -> new InetSocketAddress(addr, HTTPS_PORT))
             .collect(Collectors.toList());
 
-        // Find the local IP address which will be used to connect to TEST_HOST.
+        // Find the local and remote IP addresses which will be used to connect to TEST_HOST.
         try {
             Socket testSocket = new Socket(TEST_HOST, HTTPS_PORT);
             mLocalAddress = testSocket.getLocalAddress();
+            mTestHostAddress = testSocket.getInetAddress();
             testSocket.close();
         } catch (IOException ioe) {
             throw new AssertionError(""
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index fc25fd8..2f6c76b 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -50,16 +50,17 @@
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.assertParcelSane
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
-import kotlin.test.assertTrue
 
 private const val TEST_IMSI1 = "imsi1"
 private const val TEST_IMSI2 = "imsi2"
@@ -70,6 +71,8 @@
 @RunWith(DevSdkIgnoreRunner::class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class NetworkTemplateTest {
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
     private val mockContext = mock(Context::class.java)
     private val mockWifiInfo = mock(WifiInfo::class.java)
 
@@ -215,6 +218,18 @@
         templateNullWifiKey.assertDoesNotMatch(identWifiNullKey)
     }
 
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun testBuildTemplateMobileAll_nullSubscriberId() {
+        val templateMobileAllWithNullImsi = buildTemplateMobileAll(null)
+        val setWithNull = HashSet<String?>().apply {
+            add(null)
+        }
+        val templateFromBuilder = NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
+            .setSubscriberIds(setWithNull).build()
+        assertEquals(templateFromBuilder, templateMobileAllWithNullImsi)
+    }
+
     @Test
     fun testMobileMatches() {
         val templateMobileImsi1 = buildTemplateMobileAll(TEST_IMSI1)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 6e1debe..d9420b8 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -16,6 +16,11 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static com.android.testutils.ContextUtils.mockService;
 
 import static org.junit.Assert.assertEquals;
@@ -35,10 +40,10 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
-import android.net.INetd;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.TetheringManager;
 import android.net.TetheringManager.TetheringEventCallback;
 import android.os.Build;
@@ -74,8 +79,6 @@
     private static final LinkAddress LINKADDRV6 =
             new LinkAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64");
     private static final Network TEST_NETWORK = new Network(123);
-    private static final Network LOCAL_NETWORK = new Network(INetd.LOCAL_NET_ID);
-
     @Mock private Context mContext;
     @Mock private Dependencies mDeps;
     @Mock private ConnectivityManager mCm;
@@ -101,8 +104,13 @@
             // Test is using mockito-extended
             doCallRealMethod().when(mContext).getSystemService(TetheringManager.class);
         }
-        doReturn(true).when(mDeps).canScanOnInterface(any());
         doReturn(mTestNetworkIfaceWrapper).when(mDeps).getNetworkInterfaceByName(anyString());
+        doReturn(true).when(mTestNetworkIfaceWrapper).isUp();
+        doReturn(true).when(mLocalOnlyIfaceWrapper).isUp();
+        doReturn(true).when(mTetheredIfaceWrapper).isUp();
+        doReturn(true).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        doReturn(true).when(mLocalOnlyIfaceWrapper).supportsMulticast();
+        doReturn(true).when(mTetheredIfaceWrapper).supportsMulticast();
         doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
                 .getNetworkInterfaceByName(LOCAL_ONLY_IFACE_NAME);
         doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
@@ -208,6 +216,24 @@
         }
     }
 
+    private static NetworkCapabilities makeCapabilities(int... transports) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        for (int transport : transports) {
+            nc.addTransportType(transport);
+        }
+        return nc;
+    }
+
+    private void postNetworkAvailable(int... transports) {
+        final LinkProperties testLp = new LinkProperties();
+        testLp.setInterfaceName(TEST_IFACE_NAME);
+        testLp.setLinkAddresses(List.of(LINKADDRV4));
+        final NetworkCapabilities testNc = makeCapabilities(transports);
+        mHandler.post(() -> mNetworkCallback.onCapabilitiesChanged(TEST_NETWORK, testNc));
+        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+    }
+
     @Test
     public void testSocketRequestAndUnrequestSocket() {
         startMonitoringSockets();
@@ -217,12 +243,7 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
 
-        final LinkProperties testLp = new LinkProperties();
-        testLp.setInterfaceName(TEST_IFACE_NAME);
-        testLp.setLinkAddresses(List.of(LINKADDRV4));
-        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
-        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
-        verify(mTestNetworkIfaceWrapper).getNetworkInterface();
+        postNetworkAvailable(TRANSPORT_WIFI);
         testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
 
         final TestSocketCallback testCallback2 = new TestSocketCallback();
@@ -244,7 +265,7 @@
         verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
+        testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
 
         mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
                 List.of(TETHERED_IFACE_NAME)));
@@ -252,7 +273,7 @@
         verify(mTetheredIfaceWrapper).getNetworkInterface();
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
+        testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
 
         mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback1));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -270,14 +291,14 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        testCallback3.expectedInterfaceDestroyedForNetwork(LOCAL_NETWORK);
+        testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
 
         mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback3));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
         // Expect the socket destroy for tethered interface.
-        testCallback3.expectedInterfaceDestroyedForNetwork(LOCAL_NETWORK);
+        testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
     }
 
     @Test
@@ -289,12 +310,7 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback.expectedNoCallback();
 
-        final LinkProperties testLp = new LinkProperties();
-        testLp.setInterfaceName(TEST_IFACE_NAME);
-        testLp.setLinkAddresses(List.of(LINKADDRV4));
-        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
-        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
-        verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
+        postNetworkAvailable(TRANSPORT_WIFI);
         testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
 
         final LinkProperties newTestLp = new LinkProperties();
@@ -302,7 +318,6 @@
         newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
         mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
-        verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
         testCallback.expectedAddressesChangedForNetwork(
                 TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
     }
@@ -406,4 +421,77 @@
         verify(mTestNetworkIfaceWrapper, times(2)).getNetworkInterface();
         testCallback.expectedSocketCreatedForNetwork(otherNetwork, List.of(otherAddress));
     }
+
+    @Test
+    public void testNoSocketCreatedForCellular() {
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_CELLULAR);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testNoSocketCreatedForNonMulticastInterface() throws Exception {
+        doReturn(false).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_BLUETOOTH);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testSocketCreatedForMulticastInterface() throws Exception {
+        doReturn(true).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_BLUETOOTH);
+        testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+    }
+
+    @Test
+    public void testNoSocketCreatedForPTPInterface() throws Exception {
+        doReturn(true).when(mTestNetworkIfaceWrapper).isPointToPoint();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_BLUETOOTH);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testNoSocketCreatedForVPNInterface() throws Exception {
+        // VPN interfaces generally also have IFF_POINTOPOINT, but even if they don't, they should
+        // not be included even with TRANSPORT_WIFI.
+        doReturn(false).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_VPN, TRANSPORT_WIFI);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testSocketCreatedForWifiWithoutMulticastFlag() throws Exception {
+        doReturn(false).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_WIFI);
+        testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+    }
 }
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 04db6d3..63daebc 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -472,8 +472,7 @@
                         256L, 16L, 512L, 32L, 0L)
                 .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 64L, 3L, 1024L, 8L, 0L);
 
-        doReturn(stats).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
-                anyInt());
+        doReturn(stats).when(mDeps).getNetworkStatsDetail();
 
         final String[] ifaces = new String[]{TEST_IFACE};
         final NetworkStats res = mFactory.readNetworkStatsDetail(UID_ALL, ifaces, TAG_ALL);
@@ -488,8 +487,7 @@
         mFactory.removeUidsLocked(removedUids);
 
         // Return empty stats for reading the result of removing uids stats later.
-        doReturn(buildEmptyStats()).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
-                anyInt());
+        doReturn(buildEmptyStats()).when(mDeps).getNetworkStatsDetail();
 
         final NetworkStats removedUidsStats =
                 mFactory.readNetworkStatsDetail(UID_ALL, ifaces, TAG_ALL);
@@ -574,8 +572,7 @@
         final NetworkStats statsFromResource = parseNetworkStatsFromGoldenSample(resourceId,
                 24 /* initialSize */, true /* consumeHeader */, false /* checkActive */,
                 true /* isUidData */);
-        doReturn(statsFromResource).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
-                anyInt());
+        doReturn(statsFromResource).when(mDeps).getNetworkStatsDetail();
         return mFactory.readNetworkStatsDetail();
     }