Merge "Cleanup the public API getActiveNetworkForUid on AOSP"
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 611c828..f537e90 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -371,6 +371,7 @@
         } catch (IllegalStateException e) {
             // Silent if the rule already exists. Note that the errno EEXIST was rethrown as
             // IllegalStateException. See BpfMap#insertEntry.
+            return false;
         }
         return true;
     }
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 08ab9ca..3c2ce0f 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -141,6 +141,11 @@
 
     /**
      * Adds a tethering IPv4 offload rule to appropriate BPF map.
+     *
+     * @param downstream true if downstream, false if upstream.
+     * @param key the key to add.
+     * @param value the value to add.
+     * @return true iff the map was modified, false if the key exists or there was an error.
      */
     public abstract boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key,
             @NonNull Tether4Value value);
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 822bdf6..859f23a 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -26,6 +26,7 @@
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
 
 import android.net.INetd;
 import android.net.INetworkStackStatusCallback;
@@ -755,6 +756,9 @@
         // deprecation of any existing RA data.
 
         setRaParams(params);
+        // Be aware that updateIpv6ForwardingRules use mLastIPv6LinkProperties, so this line should
+        // be eariler than updateIpv6ForwardingRules.
+        // TODO: avoid this dependencies and move this logic into BpfCoordinator.
         mLastIPv6LinkProperties = v6only;
 
         updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null);
@@ -892,12 +896,20 @@
         mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex);
     }
 
+    private boolean isIpv6VcnNetworkInterface() {
+        if (mLastIPv6LinkProperties == null) return false;
+
+        return isVcnInterface(mLastIPv6LinkProperties.getInterfaceName());
+    }
+
     // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
     // changes or if a neighbor event is received.
     private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
             NeighborEvent e) {
-        // If we no longer have an upstream, clear forwarding rules and do nothing else.
-        if (upstreamIfindex == 0) {
+        // If no longer have an upstream or it is virtual network, clear forwarding rules and do
+        // nothing else.
+        // TODO: Rather than always clear rules, ensure whether ipv6 ever enable first.
+        if (upstreamIfindex == 0 || isIpv6VcnNetworkInterface()) {
             clearIpv6ForwardingRules();
             return;
         }
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 2c1fd29..f4a6916 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -31,6 +31,7 @@
 import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
 import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
+import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
 
 import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
@@ -123,10 +124,21 @@
         return makeMapPath((downstream ? "downstream" : "upstream") + ipVersion);
     }
 
+    // TODO: probably to remember what the timeout updated things to last is. But that requires
+    // either r/w map entries (which seems bad/racy) or a separate map to keep track of all flows
+    // and remember when they were updated and with what timeout.
     @VisibleForTesting
-    static final int POLLING_CONNTRACK_TIMEOUT_MS = 60_000;
+    static final int CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS = 60_000;
     @VisibleForTesting
-    static final int NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED = 432000;
+    static final int CONNTRACK_TIMEOUT_UPDATE_SLACK_MS = 20_000;
+
+    // Default timeouts sync from /proc/sys/net/netfilter/nf_conntrack_*
+    // See also kernel document nf_conntrack-sysctl.txt.
+    @VisibleForTesting
+    static final int NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED = 432_000;
+    static final int NF_CONNTRACK_TCP_TIMEOUT_UNACKNOWLEDGED = 300;
+    // The default value is 120 for 5.10 and that thus the periodicity of the updates of 60s is
+    // low enough to support all ACK kernels.
     @VisibleForTesting
     static final int NF_CONNTRACK_UDP_TIMEOUT_STREAM = 180;
 
@@ -249,10 +261,10 @@
         maybeSchedulePollingStats();
     };
 
-    // Runnable that used by scheduling next polling of conntrack timeout.
-    private final Runnable mScheduledPollingConntrackTimeout = () -> {
-        maybeRefreshConntrackTimeout();
-        maybeSchedulePollingConntrackTimeout();
+    // Runnable that used by scheduling next refreshing of conntrack timeout.
+    private final Runnable mScheduledConntrackTimeoutUpdate = () -> {
+        refreshAllConntrackTimeouts();
+        maybeScheduleConntrackTimeoutUpdate();
     };
 
     // TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
@@ -434,7 +446,7 @@
 
         mPollingStarted = true;
         maybeSchedulePollingStats();
-        maybeSchedulePollingConntrackTimeout();
+        maybeScheduleConntrackTimeoutUpdate();
 
         mLog.i("Polling started");
     }
@@ -451,8 +463,8 @@
         if (!mPollingStarted) return;
 
         // Stop scheduled polling conntrack timeout.
-        if (mHandler.hasCallbacks(mScheduledPollingConntrackTimeout)) {
-            mHandler.removeCallbacks(mScheduledPollingConntrackTimeout);
+        if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
+            mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
         }
         // Stop scheduled polling stats and poll the latest stats from BPF maps.
         if (mHandler.hasCallbacks(mScheduledPollingStats)) {
@@ -666,6 +678,8 @@
 
         if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return;
 
+        if (isVcnInterface(upstreamIface)) return;
+
         // The same interface index to name mapping may be added by different IpServer objects or
         // re-added by reconnection on the same upstream interface. Ignore the duplicate one.
         final String iface = mInterfaceNames.get(upstreamIfindex);
@@ -833,9 +847,10 @@
         // TODO: need to consider 464xlat.
         if (ns != null && ns.linkProperties != null && ns.linkProperties.hasIpv4Address()) {
             // TODO: support ether ip upstream interface.
-            final InterfaceParams params = mDeps.getInterfaceParams(
-                    ns.linkProperties.getInterfaceName());
-            if (params != null && !params.hasMacAddress /* raw ip upstream only */) {
+            final String ifaceName = ns.linkProperties.getInterfaceName();
+            final InterfaceParams params = mDeps.getInterfaceParams(ifaceName);
+            final boolean isVcn = isVcnInterface(ifaceName);
+            if (!isVcn && params != null && !params.hasMacAddress /* raw ip upstream only */) {
                 upstreamIndex = params.index;
             }
         }
@@ -879,6 +894,8 @@
      * TODO: consider error handling if the attach program failed.
      */
     public void maybeAttachProgram(@NonNull String intIface, @NonNull String extIface) {
+        if (isVcnInterface(extIface)) return;
+
         if (forwardingPairExists(intIface, extIface)) return;
 
         boolean firstDownstreamForThisUpstream = !isAnyForwardingPairOnUpstream(extIface);
@@ -1051,6 +1068,15 @@
         }
     }
 
+    private String l4protoToString(int proto) {
+        if (proto == OsConstants.IPPROTO_TCP) {
+            return "tcp";
+        } else if (proto == OsConstants.IPPROTO_UDP) {
+            return "udp";
+        }
+        return String.format("unknown(%d)", proto);
+    }
+
     private String ipv4RuleToString(long now, boolean downstream,
             Tether4Key key, Tether4Value value) {
         final String src4, public4, dst4;
@@ -1069,12 +1095,11 @@
             throw new AssertionError("IP address array not valid IPv4 address!");
         }
 
-        final String protoStr = (key.l4proto == OsConstants.IPPROTO_TCP) ? "tcp" : "udp";
         final String ageStr = (value.lastUsed == 0) ? "-"
                 : String.format("%dms", (now - value.lastUsed) / 1_000_000);
         return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %s",
-                protoStr, key.dstMac, key.iif, getIfName(key.iif), src4, key.srcPort,
-                value.oif, getIfName(value.oif),
+                l4protoToString(key.l4proto), key.dstMac, key.iif, getIfName(key.iif),
+                src4, key.srcPort, value.oif, getIfName(value.oif),
                 public4, publicPort, dst4, value.dstPort, value.ethDstMac, ageStr);
     }
 
@@ -1443,25 +1468,6 @@
         return addr6;
     }
 
-    @Nullable
-    private Inet4Address ipv4MappedAddressBytesToIpv4Address(final byte[] addr46) {
-        if (addr46.length != 16) return null;
-        if (addr46[0] != 0 || addr46[1] != 0 || addr46[2] != 0 || addr46[3] != 0
-                || addr46[4] != 0 || addr46[5] != 0 || addr46[6] != 0 || addr46[7] != 0
-                || addr46[8] != 0 && addr46[9] != 0 || (addr46[10] & 0xff) != 0xff
-                || (addr46[11] & 0xff) != 0xff) {
-            return null;
-        }
-
-        final byte[] addr4 = new byte[4];
-        addr4[0] = addr46[12];
-        addr4[1] = addr46[13];
-        addr4[2] = addr46[14];
-        addr4[3] = addr46[15];
-
-        return parseIPv4Address(addr4);
-    }
-
     // TODO: parse CTA_PROTOINFO of conntrack event in ConntrackMonitor. For TCP, only add rules
     // while TCP status is established.
     @VisibleForTesting
@@ -1572,8 +1578,34 @@
             final Tether4Key downstream4Key = makeTetherDownstream4Key(e, tetherClient,
                     upstreamIndex);
 
-            if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
-                    | NetlinkConstants.IPCTNL_MSG_CT_DELETE)) {
+            final boolean isConntrackEventDelete =
+                    e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+                    | NetlinkConstants.IPCTNL_MSG_CT_DELETE);
+
+            // Using the timeout to distinguish tcp state is not a decent way. Need to fix.
+            // The received IPCTNL_MSG_CT_NEW must pass ConntrackMonitor#isEstablishedNatSession
+            // which checks CTA_STATUS. It implies that this entry has at least reached tcp
+            // state "established". For safety, treat any timeout which is equal or larger than 300
+            // seconds (UNACKNOWLEDGED, ESTABLISHED, ..) to be "established".
+            // TODO: parse tcp state in conntrack monitor.
+            final boolean isTcpEstablished =
+                    e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+                    | NetlinkConstants.IPCTNL_MSG_CT_NEW)
+                    && e.tupleOrig.protoNum == OsConstants.IPPROTO_TCP
+                    && (e.timeoutSec >= NF_CONNTRACK_TCP_TIMEOUT_UNACKNOWLEDGED);
+
+            final boolean isTcpNonEstablished =
+                    e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+                    | NetlinkConstants.IPCTNL_MSG_CT_NEW)
+                    && e.tupleOrig.protoNum == OsConstants.IPPROTO_TCP
+                    && (e.timeoutSec < NF_CONNTRACK_TCP_TIMEOUT_UNACKNOWLEDGED);
+
+            // Delete the BPF rules:
+            // 1. Contrack event IPCTNL_MSG_CT_DELETE received.
+            // 2. For TCP conntrack entry, the tcp state has left "established" and going to be
+            // closed.
+            // TODO: continue to offload half-closed tcp connections.
+            if (isConntrackEventDelete || isTcpNonEstablished) {
                 final boolean deletedUpstream = mBpfCoordinatorShim.tetherOffloadRuleRemove(
                         UPSTREAM, upstream4Key);
                 final boolean deletedDownstream = mBpfCoordinatorShim.tetherOffloadRuleRemove(
@@ -1588,6 +1620,7 @@
                     Log.wtf(TAG, "The bidirectional rules should be removed concurrently ("
                             + "upstream: " + deletedUpstream
                             + ", downstream: " + deletedDownstream + ")");
+                    // TODO: consider better error handling for the stubs {rule, limit, ..}.
                     return;
                 }
 
@@ -1598,11 +1631,41 @@
             final Tether4Value upstream4Value = makeTetherUpstream4Value(e, upstreamIndex);
             final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
                     upstreamIndex);
-
             maybeAddDevMap(upstreamIndex, tetherClient.downstreamIfindex);
             maybeSetLimit(upstreamIndex);
-            mBpfCoordinatorShim.tetherOffloadRuleAdd(UPSTREAM, upstream4Key, upstream4Value);
-            mBpfCoordinatorShim.tetherOffloadRuleAdd(DOWNSTREAM, downstream4Key, downstream4Value);
+
+            final boolean upstreamAdded = mBpfCoordinatorShim.tetherOffloadRuleAdd(UPSTREAM,
+                    upstream4Key, upstream4Value);
+            final boolean downstreamAdded = mBpfCoordinatorShim.tetherOffloadRuleAdd(DOWNSTREAM,
+                    downstream4Key, downstream4Value);
+
+            if (upstreamAdded != downstreamAdded) {
+                mLog.e("The bidirectional rules should be added or not added concurrently ("
+                        + "upstream: " + upstreamAdded
+                        + ", downstream: " + downstreamAdded + "). "
+                        + "Remove the added rules.");
+                if (upstreamAdded) {
+                    mBpfCoordinatorShim.tetherOffloadRuleRemove(UPSTREAM, upstream4Key);
+                }
+                if (downstreamAdded) {
+                    mBpfCoordinatorShim.tetherOffloadRuleRemove(DOWNSTREAM, downstream4Key);
+                }
+                return;
+            }
+
+            // Update TCP timeout iif it is first time we're adding the rules. Needed because a
+            // payload data packet may have gone through non-offload path, before we added offload
+            // rules, and that this may result in in-kernel conntrack state being in ESTABLISHED
+            // but pending ACK (ie. UNACKED) state. But the in-kernel conntrack might never see the
+            // ACK because we just added offload rules. As such after adding the rules we need to
+            // force the timeout back to the normal ESTABLISHED timeout of 5 days. Note that
+            // updating the timeout will trigger another netlink event with the updated timeout.
+            // TODO: Remove this once the tcp state is parsed.
+            if (isTcpEstablished && upstreamAdded && downstreamAdded) {
+                updateConntrackTimeout((byte) upstream4Key.l4proto,
+                        parseIPv4Address(upstream4Key.src4), (short) upstream4Key.srcPort,
+                        parseIPv4Address(upstream4Key.dst4), (short) upstream4Key.dstPort);
+            }
         }
     }
 
@@ -1861,7 +1924,7 @@
         try {
             final InetAddress ia = Inet4Address.getByAddress(addrBytes);
             if (ia instanceof Inet4Address) return (Inet4Address) ia;
-        } catch (UnknownHostException | IllegalArgumentException e) {
+        } catch (UnknownHostException e) {
             mLog.e("Failed to parse IPv4 address: " + e);
         }
         return null;
@@ -1871,12 +1934,21 @@
     // coming a conntrack event to notify updated timeout.
     private void updateConntrackTimeout(byte proto, Inet4Address src4, short srcPort,
             Inet4Address dst4, short dstPort) {
-        if (src4 == null || dst4 == null) return;
+        if (src4 == null || dst4 == null) {
+            mLog.e("Either source or destination IPv4 address is invalid ("
+                    + "proto: " + proto + ", "
+                    + "src4: " + src4 + ", "
+                    + "srcPort: " + Short.toUnsignedInt(srcPort) + ", "
+                    + "dst4: " + dst4 + ", "
+                    + "dstPort: " + Short.toUnsignedInt(dstPort) + ")");
+            return;
+        }
 
         // TODO: consider acquiring the timeout setting from nf_conntrack_* variables.
         // - proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
         // - proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
         // See kernel document nf_conntrack-sysctl.txt.
+        // TODO: we should account for the fact that lastUsed is in the past and not exactly now.
         final int timeoutSec = (proto == OsConstants.IPPROTO_TCP)
                 ? NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED
                 : NF_CONNTRACK_UDP_TIMEOUT_STREAM;
@@ -1885,38 +1957,67 @@
         try {
             NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
         } catch (ErrnoException e) {
-            mLog.e("Error updating conntrack entry ("
+            // Lower the log level for the entry not existing. The conntrack entry may have been
+            // deleted and not handled by the conntrack event monitor yet. In other words, the
+            // rule has not been deleted from the BPF map yet. Deleting a non-existent entry may
+            // happen during the conntrack timeout refreshing iteration. Note that ENOENT may be
+            // a real error but is hard to distinguish.
+            // TODO: Figure out a better way to handle this.
+            final String errMsg = "Failed to update conntrack entry ("
                     + "proto: " + proto + ", "
                     + "src4: " + src4 + ", "
                     + "srcPort: " + Short.toUnsignedInt(srcPort) + ", "
                     + "dst4: " + dst4 + ", "
                     + "dstPort: " + Short.toUnsignedInt(dstPort) + "), "
                     + "msg: " + NetlinkConstants.hexify(msg) + ", "
-                    + "e: " + e);
+                    + "e: " + e;
+            if (OsConstants.ENOENT == e.errno) {
+                mLog.w(errMsg);
+            } else {
+                mLog.e(errMsg);
+            }
         }
     }
 
-    private void maybeRefreshConntrackTimeout() {
-        final long now = mDeps.elapsedRealtimeNanos();
+    boolean requireConntrackTimeoutUpdate(long nowNs, long lastUsedNs, int proto) {
+        // Refreshing tcp timeout without checking tcp state may make the conntrack entry live
+        // 5 days (432000s) even while the session is being closed. Its BPF rule may not be
+        // deleted for 5 days because the tcp state gets stuck and conntrack delete message is
+        // not sent. Note that both the conntrack monitor and refreshing timeout updater are
+        // in the same thread. Beware while the tcp status may be changed running in refreshing
+        // timeout updater and may read out-of-date tcp stats.
+        // See nf_conntrack_tcp_timeout_established in kernel document.
+        // TODO: support refreshing TCP conntrack timeout.
+        if (proto == OsConstants.IPPROTO_TCP) return false;
 
-        // Reverse the source and destination {address, port} from downstream value because
-        // #updateConntrackTimeout refresh the timeout of netlink attribute CTA_TUPLE_ORIG
-        // which is opposite direction for downstream map value.
-        mBpfCoordinatorShim.tetherOffloadRuleForEach(DOWNSTREAM, (k, v) -> {
-            if ((now - v.lastUsed) / 1_000_000 < POLLING_CONNTRACK_TIMEOUT_MS) {
-                updateConntrackTimeout((byte) k.l4proto,
-                        ipv4MappedAddressBytesToIpv4Address(v.dst46), (short) v.dstPort,
-                        ipv4MappedAddressBytesToIpv4Address(v.src46), (short) v.srcPort);
-            }
-        });
+        // The timeout requirement check needs the slack time because the scheduled timer may
+        // be not precise. The timeout update has a chance to be missed.
+        return (nowNs - lastUsedNs) < (long) (CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS
+                + CONNTRACK_TIMEOUT_UPDATE_SLACK_MS) * 1_000_000;
+    }
+
+    private void refreshAllConntrackTimeouts() {
+        final long now = mDeps.elapsedRealtimeNanos();
 
         // TODO: Consider ignoring TCP traffic on upstream and monitor on downstream only
         // because TCP is a bidirectional traffic. Probably don't need to extend timeout by
         // both directions for TCP.
         mBpfCoordinatorShim.tetherOffloadRuleForEach(UPSTREAM, (k, v) -> {
-            if ((now - v.lastUsed) / 1_000_000 < POLLING_CONNTRACK_TIMEOUT_MS) {
-                updateConntrackTimeout((byte) k.l4proto, parseIPv4Address(k.src4),
-                        (short) k.srcPort, parseIPv4Address(k.dst4), (short) k.dstPort);
+            if (requireConntrackTimeoutUpdate(now, v.lastUsed, k.l4proto)) {
+                updateConntrackTimeout((byte) k.l4proto,
+                        parseIPv4Address(k.src4), (short) k.srcPort,
+                        parseIPv4Address(k.dst4), (short) k.dstPort);
+            }
+        });
+
+        // Reverse the source and destination {address, port} from downstream value because
+        // #updateConntrackTimeout refreshes the timeout of netlink attribute CTA_TUPLE_ORIG
+        // which is opposite direction for downstream map value.
+        mBpfCoordinatorShim.tetherOffloadRuleForEach(DOWNSTREAM, (k, v) -> {
+            if (requireConntrackTimeoutUpdate(now, v.lastUsed, k.l4proto)) {
+                updateConntrackTimeout((byte) k.l4proto,
+                        parseIPv4Address(v.dst46), (short) v.dstPort,
+                        parseIPv4Address(v.src46), (short) v.srcPort);
             }
         });
     }
@@ -1931,14 +2032,15 @@
         mHandler.postDelayed(mScheduledPollingStats, getPollingInterval());
     }
 
-    private void maybeSchedulePollingConntrackTimeout() {
+    private void maybeScheduleConntrackTimeoutUpdate() {
         if (!mPollingStarted) return;
 
-        if (mHandler.hasCallbacks(mScheduledPollingConntrackTimeout)) {
-            mHandler.removeCallbacks(mScheduledPollingConntrackTimeout);
+        if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
+            mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
         }
 
-        mHandler.postDelayed(mScheduledPollingConntrackTimeout, POLLING_CONNTRACK_TIMEOUT_MS);
+        mHandler.postDelayed(mScheduledConntrackTimeoutUpdate,
+                CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
     }
 
     // Return forwarding rule map. This is used for testing only.
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index 69471a1..5584db2 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -49,7 +49,6 @@
 import com.android.internal.util.StateMachine;
 import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
 import com.android.networkstack.apishim.common.ConnectivityManagerShim;
-import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -162,12 +161,7 @@
         }
         ConnectivityManagerShim mCmShim = ConnectivityManagerShimImpl.newInstance(mContext);
         mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
-        try {
-            mCmShim.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
-        } catch (UnsupportedApiLevelException e) {
-            Log.wtf(TAG, "registerSystemDefaultNetworkCallback is not supported");
-            return;
-        }
+        mCmShim.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
         if (mEntitlementMgr == null) {
             mEntitlementMgr = entitle;
         }
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java
index bab9f84..986c3f7 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java
@@ -15,6 +15,8 @@
  */
 package com.android.networkstack.tethering;
 
+import static android.net.INetd.IPSEC_INTERFACE_PREFIX;
+
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -48,4 +50,9 @@
                 networkCapabilities == null ? "null" : networkCapabilities,
                 linkProperties == null ? "null" : linkProperties);
     }
+
+    /** Check whether the interface is VCN. */
+    public static boolean isVcnInterface(@NonNull String iface) {
+        return iface.startsWith(IPSEC_INTERFACE_PREFIX);
+    }
 }
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 378a21c..6bf6a9f 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -146,8 +146,10 @@
     private static final String IFACE_NAME = "testnet1";
     private static final String UPSTREAM_IFACE = "upstream0";
     private static final String UPSTREAM_IFACE2 = "upstream1";
+    private static final String IPSEC_IFACE = "ipsec0";
     private static final int UPSTREAM_IFINDEX = 101;
     private static final int UPSTREAM_IFINDEX2 = 102;
+    private static final int IPSEC_IFINDEX = 103;
     private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
     private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
     private static final int DHCP_LEASE_TIME_SECS = 3600;
@@ -160,6 +162,8 @@
     private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams(
             UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.ALL_ZEROS_ADDRESS,
             1500 /* defaultMtu */);
+    private static final InterfaceParams IPSEC_IFACE_PARAMS = new InterfaceParams(
+            IPSEC_IFACE, IPSEC_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
 
     private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
 
@@ -208,6 +212,7 @@
         when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
         when(mDependencies.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
         when(mDependencies.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
+        when(mDependencies.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS);
 
         mInterfaceConfiguration = new InterfaceConfigurationParcel();
         mInterfaceConfiguration.flags = new String[0];
@@ -1453,4 +1458,23 @@
     public void testDadProxyUpdates_EnabledAfterR() throws Exception {
         checkDadProxyEnabled(true);
     }
+
+    @Test
+    public void testSkipVirtualNetworkInBpf() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
+        final LinkProperties v6Only = new LinkProperties();
+        v6Only.setInterfaceName(IPSEC_IFACE);
+        dispatchTetherConnectionChanged(IPSEC_IFACE, v6Only, 0);
+
+        verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, IPSEC_IFACE);
+        verify(mNetd).tetherAddForward(IFACE_NAME, IPSEC_IFACE);
+        verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, IPSEC_IFACE);
+
+        final int myIfindex = TEST_IFACE_PARAMS.index;
+        final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
+        final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
+        recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, mac);
+        verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(
+                mIpServer, makeForwardingRule(IPSEC_IFINDEX, neigh, mac));
+    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 914e0d4..ab542d6 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -40,9 +40,10 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS;
+import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_SLACK_MS;
 import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED;
 import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_UDP_TIMEOUT_STREAM;
-import static com.android.networkstack.tethering.BpfCoordinator.POLLING_CONNTRACK_TIMEOUT_MS;
 import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
 import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
 import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
@@ -1128,6 +1129,7 @@
             final String intIface1 = "wlan1";
             final String intIface2 = "rndis0";
             final String extIface = "rmnet_data0";
+            final String virtualIface = "ipsec0";
             final BpfUtils mockMarkerBpfUtils = staticMockMarker(BpfUtils.class);
             final BpfCoordinator coordinator = makeBpfCoordinator();
 
@@ -1163,6 +1165,14 @@
             ExtendedMockito.verify(() -> BpfUtils.detachProgram(intIface1));
             ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
             ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+
+            // [6] Skip attaching if upstream is virtual interface.
+            coordinator.maybeAttachProgram(intIface1, virtualIface);
+            ExtendedMockito.verify(() -> BpfUtils.attachProgram(extIface, DOWNSTREAM), never());
+            ExtendedMockito.verify(() -> BpfUtils.attachProgram(intIface1, UPSTREAM), never());
+            ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
+            ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+
         } finally {
             mockSession.finishMocking();
         }
@@ -1369,8 +1379,14 @@
         }
 
         final int status = (msgType == IPCTNL_MSG_CT_NEW) ? ESTABLISHED_MASK : DYING_MASK;
-        final int timeoutSec = (msgType == IPCTNL_MSG_CT_NEW) ? 100 /* nonzero, new */
-                : 0 /* unused, delete */;
+        int timeoutSec;
+        if (msgType == IPCTNL_MSG_CT_NEW) {
+            timeoutSec = (proto == IPPROTO_TCP)
+                    ? NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED
+                    : NF_CONNTRACK_UDP_TIMEOUT_STREAM;
+        } else {
+            timeoutSec = 0; /* unused, CT_DELETE */
+        }
         return new ConntrackEvent(
                 (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
                 new Tuple(new TupleIpv4(PRIVATE_ADDR, REMOTE_ADDR),
@@ -1526,23 +1542,27 @@
     }
 
     private void checkRefreshConntrackTimeout(final TestBpfMap<Tether4Key, Tether4Value> bpfMap,
-            final Tether4Key tcpKey, final Tether4Value tcpValue, final Tether4Key udpKey,
-            final Tether4Value udpValue) throws Exception {
+            int proto, final Tether4Key key, final Tether4Value value) throws Exception {
+        if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
+            fail("Not support protocol " + proto);
+        }
         // Both system elapsed time since boot and the rule last used time are used to measure
         // the rule expiration. In this test, all test rules are fixed the last used time to 0.
         // Set the different testing elapsed time to make the rule to be valid or expired.
         //
         // Timeline:
-        // 0                                       60 (seconds)
-        // +---+---+---+---+--...--+---+---+---+---+---+- ..
-        // |      POLLING_CONNTRACK_TIMEOUT_MS     |
-        // +---+---+---+---+--...--+---+---+---+---+---+- ..
-        // |<-          valid diff           ->|
-        // |<-          expired diff                 ->|
-        // ^                                   ^       ^
-        // last used time      elapsed time (valid)    elapsed time (expired)
-        final long validTime = (POLLING_CONNTRACK_TIMEOUT_MS - 1) * 1_000_000L;
-        final long expiredTime = (POLLING_CONNTRACK_TIMEOUT_MS + 1) * 1_000_000L;
+        //                                    CONNTRACK_TIMEOUT_UPDATE_SLACK_MS
+        //                                               ->|    |<-
+        // +---+---+---+---+---+--...--+---+---+---+---+---+-..-+---+-- ..
+        // |     CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS      |
+        // +---+---+---+---+---+--...--+---+---+---+---+---+-..-+---+-- ..
+        // |<-                valid diff                 ->|
+        // |<-                expired diff                        ->|
+        // ^                                               ^        ^
+        // last used time               elapsed time (valid)    elapsed time (expired)
+        final long validTimeNs = CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS * 1_000_000L;
+        final long expiredTimeNs = (CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS
+                + CONNTRACK_TIMEOUT_UPDATE_SLACK_MS + 1) * 1_000_000L;
 
         // Static mocking for NetlinkSocket.
         MockitoSession mockSession = ExtendedMockito.mockitoSession()
@@ -1551,36 +1571,33 @@
         try {
             final BpfCoordinator coordinator = makeBpfCoordinator();
             coordinator.startPolling();
-            bpfMap.insertEntry(tcpKey, tcpValue);
-            bpfMap.insertEntry(udpKey, udpValue);
+            bpfMap.insertEntry(key, value);
 
             // [1] Don't refresh contrack timeout.
-            setElapsedRealtimeNanos(expiredTime);
-            mTestLooper.moveTimeForward(POLLING_CONNTRACK_TIMEOUT_MS);
+            setElapsedRealtimeNanos(expiredTimeNs);
+            mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
             waitForIdle();
             ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
             ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
 
-            // [2] Refresh contrack timeout.
-            setElapsedRealtimeNanos(validTime);
-            mTestLooper.moveTimeForward(POLLING_CONNTRACK_TIMEOUT_MS);
+            // [2] Refresh contrack timeout. UDP Only.
+            setElapsedRealtimeNanos(validTimeNs);
+            mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
             waitForIdle();
-            final byte[] expectedNetlinkTcp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
-                    IPPROTO_TCP, PRIVATE_ADDR, (int) PRIVATE_PORT, REMOTE_ADDR,
-                    (int) REMOTE_PORT, NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED);
-            final byte[] expectedNetlinkUdp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
-                    IPPROTO_UDP, PRIVATE_ADDR, (int) PRIVATE_PORT, REMOTE_ADDR,
-                    (int) REMOTE_PORT, NF_CONNTRACK_UDP_TIMEOUT_STREAM);
-            ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
-                    eq(NETLINK_NETFILTER), eq(expectedNetlinkTcp)));
-            ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
-                    eq(NETLINK_NETFILTER), eq(expectedNetlinkUdp)));
+
+            if (proto == IPPROTO_UDP) {
+                final byte[] expectedNetlinkUdp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+                        IPPROTO_UDP, PRIVATE_ADDR, (int) PRIVATE_PORT, REMOTE_ADDR,
+                        (int) REMOTE_PORT, NF_CONNTRACK_UDP_TIMEOUT_STREAM);
+                ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
+                        eq(NETLINK_NETFILTER), eq(expectedNetlinkUdp)));
+            }
             ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
             ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
 
             // [3] Don't refresh contrack timeout if polling stopped.
             coordinator.stopPolling();
-            mTestLooper.moveTimeForward(POLLING_CONNTRACK_TIMEOUT_MS);
+            mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
             waitForIdle();
             ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
             ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
@@ -1591,33 +1608,57 @@
 
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testRefreshConntrackTimeout_Upstream4Map() throws Exception {
+    public void testRefreshConntrackTimeout_Upstream4MapTcp() throws Exception {
         // TODO: Replace the dependencies BPF map with a non-mocked TestBpfMap object.
         final TestBpfMap<Tether4Key, Tether4Value> bpfUpstream4Map =
                 new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
         doReturn(bpfUpstream4Map).when(mDeps).getBpfUpstream4Map();
 
-        final Tether4Key tcpKey = makeUpstream4Key(IPPROTO_TCP);
-        final Tether4Key udpKey = makeUpstream4Key(IPPROTO_UDP);
-        final Tether4Value tcpValue = makeUpstream4Value();
-        final Tether4Value udpValue = makeUpstream4Value();
+        final Tether4Key key = makeUpstream4Key(IPPROTO_TCP);
+        final Tether4Value value = makeUpstream4Value();
 
-        checkRefreshConntrackTimeout(bpfUpstream4Map, tcpKey, tcpValue, udpKey, udpValue);
+        checkRefreshConntrackTimeout(bpfUpstream4Map, IPPROTO_TCP, key, value);
     }
 
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testRefreshConntrackTimeout_Downstream4Map() throws Exception {
+    public void testRefreshConntrackTimeout_Upstream4MapUdp() throws Exception {
+        // TODO: Replace the dependencies BPF map with a non-mocked TestBpfMap object.
+        final TestBpfMap<Tether4Key, Tether4Value> bpfUpstream4Map =
+                new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
+        doReturn(bpfUpstream4Map).when(mDeps).getBpfUpstream4Map();
+
+        final Tether4Key key = makeUpstream4Key(IPPROTO_UDP);
+        final Tether4Value value = makeUpstream4Value();
+
+        checkRefreshConntrackTimeout(bpfUpstream4Map, IPPROTO_UDP, key, value);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testRefreshConntrackTimeout_Downstream4MapTcp() throws Exception {
         // TODO: Replace the dependencies BPF map with a non-mocked TestBpfMap object.
         final TestBpfMap<Tether4Key, Tether4Value> bpfDownstream4Map =
                 new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
         doReturn(bpfDownstream4Map).when(mDeps).getBpfDownstream4Map();
 
-        final Tether4Key tcpKey = makeDownstream4Key(IPPROTO_TCP);
-        final Tether4Key udpKey = makeDownstream4Key(IPPROTO_UDP);
-        final Tether4Value tcpValue = makeDownstream4Value();
-        final Tether4Value udpValue = makeDownstream4Value();
+        final Tether4Key key = makeDownstream4Key(IPPROTO_TCP);
+        final Tether4Value value = makeDownstream4Value();
 
-        checkRefreshConntrackTimeout(bpfDownstream4Map, tcpKey, tcpValue, udpKey, udpValue);
+        checkRefreshConntrackTimeout(bpfDownstream4Map, IPPROTO_TCP, key, value);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testRefreshConntrackTimeout_Downstream4MapUdp() throws Exception {
+        // TODO: Replace the dependencies BPF map with a non-mocked TestBpfMap object.
+        final TestBpfMap<Tether4Key, Tether4Value> bpfDownstream4Map =
+                new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
+        doReturn(bpfDownstream4Map).when(mDeps).getBpfDownstream4Map();
+
+        final Tether4Key key = makeDownstream4Key(IPPROTO_UDP);
+        final Tether4Value value = makeDownstream4Value();
+
+        checkRefreshConntrackTimeout(bpfDownstream4Map, IPPROTO_UDP, key, value);
     }
 }
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 5caa11b..2cd0220 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,17 +1,89 @@
-rule android.sysprop.** com.android.connectivity.sysprop.@1
-rule com.android.net.module.util.** com.android.connectivity.net-utils.@1
-rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1
+rule android.sysprop.** com.android.connectivity.@0
+rule com.android.net.module.util.** com.android.connectivity.@0
+rule com.android.modules.utils.** com.android.connectivity.@0
 
 # internal util classes
-# Exclude AsyncChannel. TODO: remove AsyncChannel usage in ConnectivityService
-rule com.android.internal.util.AsyncChannel* @0
-# Exclude LocationPermissionChecker. This is going to be moved to libs/net
-rule com.android.internal.util.LocationPermissionChecker* @0
-rule android.util.LocalLog* com.android.connectivity.util.LocalLog@1
+rule android.util.LocalLog* com.android.connectivity.@0
 # android.util.IndentingPrintWriter* should use a different package name from
 # the one in com.android.internal.util
-rule android.util.IndentingPrintWriter* android.connectivity.util.IndentingPrintWriter@1
-rule com.android.internal.util.** com.android.connectivity.util.@1
+rule android.util.IndentingPrintWriter* com.android.connectivity.@0
+rule com.android.internal.util.** com.android.connectivity.@0
 
-rule com.android.internal.messages.** com.android.connectivity.messages.@1
-rule com.google.protobuf.** com.android.connectivity.protobuf.@1
+rule com.android.internal.messages.** com.android.connectivity.@0
+rule com.google.protobuf.** com.android.connectivity.@0
+
+# From dnsresolver_aidl_interface (newer AIDLs should go to android.net.resolv.aidl)
+rule android.net.resolv.aidl.** com.android.connectivity.@0
+rule android.net.IDnsResolver* com.android.connectivity.@0
+rule android.net.ResolverHostsParcel* com.android.connectivity.@0
+rule android.net.ResolverOptionsParcel* com.android.connectivity.@0
+rule android.net.ResolverParamsParcel* com.android.connectivity.@0
+rule android.net.ResolverParamsParcel* com.android.connectivity.@0
+# Also includes netd event listener AIDL, but this is handled by netd-client rules
+
+# From net-utils-device-common
+rule android.net.NetworkFactory* com.android.connectivity.@0
+
+# From netd-client (newer AIDLs should go to android.net.netd.aidl)
+rule android.net.netd.aidl.** com.android.connectivity.@0
+rule android.net.INetd* com.android.connectivity.@0
+rule android.net.InterfaceConfigurationParcel* com.android.connectivity.@0
+rule android.net.MarkMaskParcel* com.android.connectivity.@0
+rule android.net.NativeNetworkConfig* com.android.connectivity.@0
+rule android.net.NativeNetworkType* com.android.connectivity.@0
+rule android.net.NativeVpnType* com.android.connectivity.@0
+rule android.net.RouteInfoParcel* com.android.connectivity.@0
+rule android.net.TetherConfigParcel* com.android.connectivity.@0
+rule android.net.TetherOffloadRuleParcel* com.android.connectivity.@0
+rule android.net.TetherStatsParcel* com.android.connectivity.@0
+rule android.net.UidRangeParcel* com.android.connectivity.@0
+rule android.net.metrics.INetdEventListener* com.android.connectivity.@0
+
+# From netlink-client
+rule android.net.netlink.** com.android.connectivity.@0
+
+# From networkstack-client (newer AIDLs should go to android.net.[networkstack|ipmemorystore].aidl)
+rule android.net.networkstack.aidl.** com.android.connectivity.@0
+rule android.net.ipmemorystore.aidl.** com.android.connectivity.@0
+rule android.net.ipmemorystore.aidl.** com.android.connectivity.@0
+rule android.net.DataStallReportParcelable* com.android.connectivity.@0
+rule android.net.DhcpResultsParcelable* com.android.connectivity.@0
+rule android.net.IIpMemoryStore* com.android.connectivity.@0
+rule android.net.INetworkMonitor* com.android.connectivity.@0
+rule android.net.INetworkStackConnector* com.android.connectivity.@0
+rule android.net.INetworkStackStatusCallback* com.android.connectivity.@0
+rule android.net.InformationElementParcelable* com.android.connectivity.@0
+rule android.net.InitialConfigurationParcelable* com.android.connectivity.@0
+rule android.net.IpMemoryStore* com.android.connectivity.@0
+rule android.net.Layer2InformationParcelable* com.android.connectivity.@0
+rule android.net.Layer2PacketParcelable* com.android.connectivity.@0
+rule android.net.NattKeepalivePacketDataParcelable* com.android.connectivity.@0
+rule android.net.NetworkMonitorManager* com.android.connectivity.@0
+rule android.net.NetworkTestResultParcelable* com.android.connectivity.@0
+rule android.net.PrivateDnsConfigParcel* com.android.connectivity.@0
+rule android.net.ProvisioningConfigurationParcelable* com.android.connectivity.@0
+rule android.net.ScanResultInfoParcelable* com.android.connectivity.@0
+rule android.net.TcpKeepalivePacketDataParcelable* com.android.connectivity.@0
+rule android.net.dhcp.DhcpLeaseParcelable* com.android.connectivity.@0
+rule android.net.dhcp.DhcpServingParamsParcel* com.android.connectivity.@0
+rule android.net.dhcp.IDhcpEventCallbacks* com.android.connectivity.@0
+rule android.net.dhcp.IDhcpServer* com.android.connectivity.@0
+rule android.net.ip.IIpClient* com.android.connectivity.@0
+rule android.net.ip.IpClientCallbacks* com.android.connectivity.@0
+rule android.net.ip.IpClientManager* com.android.connectivity.@0
+rule android.net.ip.IpClientUtil* com.android.connectivity.@0
+rule android.net.ipmemorystore.** com.android.connectivity.@0
+rule android.net.networkstack.** com.android.connectivity.@0
+rule android.net.shared.** com.android.connectivity.@0
+rule android.net.util.KeepalivePacketDataUtil* com.android.connectivity.@0
+
+# From connectivity-module-utils
+rule android.net.util.InterfaceParams* com.android.connectivity.@0
+rule android.net.util.SharedLog* com.android.connectivity.@0
+rule android.net.shared.** com.android.connectivity.@0
+
+# From services-connectivity-shared-srcs
+rule android.net.util.NetworkConstants* com.android.connectivity.@0
+
+# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
+# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 41b093e..770aa8a 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -428,7 +428,7 @@
     // PREFERENCE_PRIORITY_NONE when sending to netd.
     static final int PREFERENCE_PRIORITY_DEFAULT = 1000;
     // As a security feature, VPNs have the top priority.
-    static final int PREFERENCE_PRIORITY_VPN = 1;
+    static final int PREFERENCE_PRIORITY_VPN = 0; // Netd supports only 0 for VPN.
     // Priority of per-app OEM preference. See {@link #setOemNetworkPreference}.
     @VisibleForTesting
     static final int PREFERENCE_PRIORITY_OEM = 10;
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index a30d4f1..9537786 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -49,6 +49,7 @@
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+import static com.android.net.module.util.NetworkCapabilitiesUtils.TRANSPORT_USB;
 import static com.android.testutils.MiscAsserts.assertEmpty;
 import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.ParcelUtils.assertParcelSane;
@@ -973,6 +974,11 @@
         assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
     }
 
+    private int getMaxTransport() {
+        if (!isAtLeastS() && MAX_TRANSPORT == TRANSPORT_USB) return MAX_TRANSPORT - 1;
+        return MAX_TRANSPORT;
+    }
+
     @Test
     public void testSignalStrength() {
         final NetworkCapabilities nc = new NetworkCapabilities();
@@ -984,7 +990,7 @@
     }
 
     private void assertNoTransport(NetworkCapabilities nc) {
-        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+        for (int i = MIN_TRANSPORT; i <= getMaxTransport(); i++) {
             assertFalse(nc.hasTransport(i));
         }
     }
@@ -1001,7 +1007,7 @@
                 assertFalse(nc.hasTransport(i));
             }
         }
-        for (int i = MAX_TRANSPORT; i > maxTransportType; i--) {
+        for (int i = getMaxTransport(); i > maxTransportType; i--) {
             if (positiveSequence) {
                 assertFalse(nc.hasTransport(i));
             } else {
@@ -1015,12 +1021,12 @@
         final NetworkCapabilities nc = new NetworkCapabilities();
         assertNoTransport(nc);
         // Test adding multiple transport types.
-        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+        for (int i = MIN_TRANSPORT; i <= getMaxTransport(); i++) {
             nc.addTransportType(i);
             checkCurrentTransportTypes(nc, i, true /* positiveSequence */);
         }
         // Test removing multiple transport types.
-        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+        for (int i = MIN_TRANSPORT; i <= getMaxTransport(); i++) {
             nc.removeTransportType(i);
             checkCurrentTransportTypes(nc, i, false /* positiveSequence */);
         }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 5fe478f..7c42811 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -37,7 +37,7 @@
         "kotlin-reflect",
         "mockito-target-extended-minus-junit4",
         "net-tests-utils",
-        "service-connectivity",
+        "service-connectivity-pre-jarjar",
         "services.core",
         "services.net",
         "testables",
@@ -52,6 +52,7 @@
         "libnativehelper_compat_libc++",
         "libnetworkstackutilsjni",
     ],
+    jarjar_rules: ":connectivity-jarjar-rules",
 }
 
 // Utilities for testing framework code both in integration and unit tests.
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index f59d0d2..e28f3c4 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -176,6 +176,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import static java.util.Arrays.asList;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -309,7 +311,6 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -1603,9 +1604,8 @@
 
         MockitoAnnotations.initMocks(this);
 
-        when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO));
-        when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
-                Arrays.asList(PRIMARY_USER_HANDLE));
+        when(mUserManager.getAliveUsers()).thenReturn(asList(PRIMARY_USER_INFO));
+        when(mUserManager.getUserHandles(anyBoolean())).thenReturn(asList(PRIMARY_USER_HANDLE));
         when(mUserManager.getUserInfo(PRIMARY_USER)).thenReturn(PRIMARY_USER_INFO);
         // canHaveRestrictedProfile does not take a userId. It applies to the userId of the context
         // it was started from, i.e., PRIMARY_USER.
@@ -1813,7 +1813,7 @@
                 eq(UserHandle.getCallingUserId()))).thenReturn(myPackageInfo);
 
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
-                Arrays.asList(new PackageInfo[] {
+                asList(new PackageInfo[] {
                         buildPackageInfo(/* SYSTEM */ false, APP1_UID),
                         buildPackageInfo(/* SYSTEM */ false, APP2_UID),
                         buildPackageInfo(/* SYSTEM */ false, VPN_UID)
@@ -1830,7 +1830,7 @@
         ResolveInfo rInfo = new ResolveInfo();
         rInfo.serviceInfo = new ServiceInfo();
         rInfo.serviceInfo.metaData = new Bundle();
-        final List<ResolveInfo> services = Arrays.asList(new ResolveInfo[]{rInfo});
+        final List<ResolveInfo> services = asList(new ResolveInfo[]{rInfo});
         when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
                 eq(userId))).thenReturn(services);
         when(mPackageManager.getPackageUidAsUser(TEST_PACKAGE_NAME, userId))
@@ -4847,8 +4847,8 @@
         final ContentResolver cr = mServiceContext.getContentResolver();
         final String settingName = ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
 
-        for (int config : Arrays.asList(0, 3, 2)) {
-            for (String setting: Arrays.asList(null, "0", "2", "1")) {
+        for (int config : asList(0, 3, 2)) {
+            for (String setting: asList(null, "0", "2", "1")) {
                 mPolicyTracker.mConfigMeteredMultipathPreference = config;
                 Settings.Global.putString(cr, settingName, setting);
                 mPolicyTracker.reevaluate();
@@ -6007,10 +6007,10 @@
         networkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, networkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
         networkCallback.assertNoCallback();
-        checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address),
-                Arrays.asList(myIpv4DefaultRoute));
+        checkDirectlyConnectedRoutes(cbi.getLp(), asList(myIpv4Address),
+                asList(myIpv4DefaultRoute));
         checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
-                Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute));
+                asList(myIpv4Address), asList(myIpv4DefaultRoute));
 
         // Verify direct routes are added during subsequent link properties updates.
         LinkProperties newLp = new LinkProperties(lp);
@@ -6022,8 +6022,8 @@
         cbi = networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, networkAgent);
         networkCallback.assertNoCallback();
         checkDirectlyConnectedRoutes(cbi.getLp(),
-                Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
-                Arrays.asList(myIpv4DefaultRoute));
+                asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
+                asList(myIpv4DefaultRoute));
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
@@ -6351,9 +6351,9 @@
                 mResolverParamsParcelCaptor.capture());
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(1, resolvrParams.servers.length);
-        assertTrue(ArrayUtils.contains(resolvrParams.servers, "2001:db8::1"));
+        assertTrue(CollectionUtils.contains(resolvrParams.servers, "2001:db8::1"));
         // Opportunistic mode.
-        assertTrue(ArrayUtils.contains(resolvrParams.tlsServers, "2001:db8::1"));
+        assertTrue(CollectionUtils.contains(resolvrParams.tlsServers, "2001:db8::1"));
         reset(mMockDnsResolver);
 
         cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
@@ -6363,12 +6363,12 @@
                 mResolverParamsParcelCaptor.capture());
         resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.servers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+        assertTrue(new ArraySet<>(resolvrParams.servers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         // Opportunistic mode.
         assertEquals(2, resolvrParams.tlsServers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         reset(mMockDnsResolver);
 
         final String TLS_SPECIFIER = "tls.example.com";
@@ -6383,8 +6383,8 @@
                 mResolverParamsParcelCaptor.capture());
         resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.servers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
-                new String[]{"2001:db8::1", "192.0.2.1"}));
+        assertTrue(new ArraySet<>(resolvrParams.servers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         reset(mMockDnsResolver);
     }
 
@@ -6491,12 +6491,12 @@
                 mResolverParamsParcelCaptor.capture());
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.tlsServers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[] { "2001:db8::1", "192.0.2.1" }));
+        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         // Opportunistic mode.
         assertEquals(2, resolvrParams.tlsServers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[] { "2001:db8::1", "192.0.2.1" }));
+        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         reset(mMockDnsResolver);
         cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
         cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
@@ -6513,8 +6513,8 @@
                 mResolverParamsParcelCaptor.capture());
         resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.servers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
-                new String[] { "2001:db8::1", "192.0.2.1" }));
+        assertTrue(new ArraySet<>(resolvrParams.servers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         reset(mMockDnsResolver);
         cellNetworkCallback.assertNoCallback();
 
@@ -6523,11 +6523,11 @@
                 mResolverParamsParcelCaptor.capture());
         resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(2, resolvrParams.servers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
-                new String[] { "2001:db8::1", "192.0.2.1" }));
+        assertTrue(new ArraySet<>(resolvrParams.servers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         assertEquals(2, resolvrParams.tlsServers.length);
-        assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
-                new String[] { "2001:db8::1", "192.0.2.1" }));
+        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
+                asList("2001:db8::1", "192.0.2.1")));
         reset(mMockDnsResolver);
         cellNetworkCallback.assertNoCallback();
 
@@ -7563,7 +7563,7 @@
         // Start the restricted profile, and check that the UID within it loses network access.
         when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER))
                 .thenReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID));
-        when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO,
+        when(mUserManager.getAliveUsers()).thenReturn(asList(PRIMARY_USER_INFO,
                 RESTRICTED_USER_INFO));
         // TODO: check that VPN app within restricted profile still has access, etc.
         final Intent addedIntent = new Intent(ACTION_USER_ADDED);
@@ -7574,7 +7574,7 @@
         assertNull(mCm.getActiveNetworkForUid(restrictedUid));
 
         // Stop the restricted profile, and check that the UID within it has network access again.
-        when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO));
+        when(mUserManager.getAliveUsers()).thenReturn(asList(PRIMARY_USER_INFO));
 
         // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
@@ -7999,7 +7999,7 @@
     // networks, ConnectivityService does not guarantee the order in which callbacks are fired.
     private void assertBlockedCallbackInAnyOrder(TestNetworkCallback callback, boolean blocked,
             TestNetworkAgentWrapper... agents) {
-        final List<Network> expectedNetworks = Arrays.asList(agents).stream()
+        final List<Network> expectedNetworks = asList(agents).stream()
                 .map((agent) -> agent.getNetwork())
                 .collect(Collectors.toList());
 
@@ -8695,7 +8695,7 @@
                 mResolverParamsParcelCaptor.capture());
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
         assertEquals(1, resolvrParams.servers.length);
-        assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
+        assertTrue(CollectionUtils.contains(resolvrParams.servers, "8.8.8.8"));
 
         for (final LinkProperties stackedLp : stackedLpsAfterChange) {
             verify(mDeps).reportNetworkInterfaceForTransports(
@@ -9368,7 +9368,7 @@
         InOrder inOrder = inOrder(mMockNetd);
 
         // Update to new range which is old range minus APP1, i.e. only APP2
-        final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
+        final Set<UidRange> newRanges = new HashSet<>(asList(
                 new UidRange(vpnRange.start, APP1_UID - 1),
                 new UidRange(APP1_UID + 1, vpnRange.stop)));
         mMockVpn.setUids(newRanges);
@@ -10510,7 +10510,7 @@
         verify(mProxyTracker, never()).sendProxyBroadcast();
 
         // Update to new range which is old range minus APP1, i.e. only APP2
-        final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
+        final Set<UidRange> newRanges = new HashSet<>(asList(
                 new UidRange(vpnRange.start, APP1_UID - 1),
                 new UidRange(APP1_UID + 1, vpnRange.stop)));
         mMockVpn.setUids(newRanges);
@@ -11111,7 +11111,7 @@
         final int secondUserTestPackageUid = UserHandle.getUid(SECONDARY_USER, TEST_PACKAGE_UID);
         final int thirdUserTestPackageUid = UserHandle.getUid(TERTIARY_USER, TEST_PACKAGE_UID);
         when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
-                Arrays.asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE, TERTIARY_USER_HANDLE));
+                asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE, TERTIARY_USER_HANDLE));
 
         // Arrange PackageManager mocks testing for users who have and don't have a package.
         mockGetApplicationInfoThrowsNameNotFound(TEST_PACKAGE_NAME, PRIMARY_USER_HANDLE);
@@ -12058,7 +12058,7 @@
         final int secondUser = 10;
         final UserHandle secondUserHandle = new UserHandle(secondUser);
         when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
-                Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle));
+                asList(PRIMARY_USER_HANDLE, secondUserHandle));
 
         // Arrange PackageManager mocks
         final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID);
@@ -12099,7 +12099,7 @@
         final int secondUser = 10;
         final UserHandle secondUserHandle = new UserHandle(secondUser);
         when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
-                Arrays.asList(PRIMARY_USER_HANDLE));
+                asList(PRIMARY_USER_HANDLE));
 
         // Arrange PackageManager mocks
         final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID);
@@ -12127,7 +12127,7 @@
 
         // Send a broadcast indicating a user was added.
         when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
-                Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle));
+                asList(PRIMARY_USER_HANDLE, secondUserHandle));
         final Intent addedIntent = new Intent(ACTION_USER_ADDED);
         addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser));
         processBroadcast(addedIntent);
@@ -12140,7 +12140,7 @@
 
         // Send a broadcast indicating a user was removed.
         when(mUserManager.getUserHandles(anyBoolean())).thenReturn(
-                Arrays.asList(PRIMARY_USER_HANDLE));
+                asList(PRIMARY_USER_HANDLE));
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
         removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser));
         processBroadcast(removedIntent);