Support seamless handover between VPN fds.
This used to work before SOCK_DESTROY allowed us to close
connections when VPNs connected and disconnected. Instead of
doing this like the old code did by registering a new
NetworkAgent, support (limited) seamless handover on the
same NetworkAgent and netId.
Bug: 64692591
Test: cts-tradefed run commandAndExit cts-dev -m CtsHostsideNetworkTests -t com.android.cts.net.HostsideVpnTests#testSeamlessHandover
Change-Id: Idad0ec5946e7eb9e1f4a13c92ea7138de6a46f16
Merged-In: Idad0ec5946e7eb9e1f4a13c92ea7138de6a46f16
(cherry picked from commit d9dbfa65c662e9b4f8064191663252925161dbfc)
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bce735b..2a80f0e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -115,6 +115,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -895,6 +896,42 @@
.compareTo(MOST_IPV6_ADDRESSES_COUNT) >= 0;
}
+ /**
+ * Attempt to perform a seamless handover of VPNs by only updating LinkProperties without
+ * registering a new NetworkAgent. This is not always possible if the new VPN configuration
+ * has certain changes, in which case this method would just return {@code false}.
+ */
+ private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
+ // NetworkMisc cannot be updated without registering a new NetworkAgent.
+ if (oldConfig.allowBypass != mConfig.allowBypass) {
+ Log.i(TAG, "Handover not possible due to changes to allowBypass");
+ return false;
+ }
+
+ // TODO: we currently do not support seamless handover if the allowed or disallowed
+ // applications have changed. Consider diffing UID ranges and only applying the delta.
+ if (!Objects.equals(oldConfig.allowedApplications, mConfig.allowedApplications) ||
+ !Objects.equals(oldConfig.disallowedApplications, mConfig.disallowedApplications)) {
+ Log.i(TAG, "Handover not possible due to changes to whitelisted/blacklisted apps");
+ return false;
+ }
+
+ LinkProperties lp = makeLinkProperties();
+ final boolean hadInternetCapability = mNetworkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ final boolean willHaveInternetCapability = providesRoutesToMostDestinations(lp);
+ if (hadInternetCapability != willHaveInternetCapability) {
+ // A seamless handover would have led to a change to INTERNET capability, which
+ // is supposed to be immutable for a given network. In this case bail out and do not
+ // perform handover.
+ Log.i(TAG, "Handover not possible due to changes to INTERNET capability");
+ return false;
+ }
+
+ agent.sendLinkProperties(lp);
+ return true;
+ }
+
private void agentConnect() {
LinkProperties lp = makeLinkProperties();
@@ -1003,13 +1040,11 @@
String oldInterface = mInterface;
Connection oldConnection = mConnection;
NetworkAgent oldNetworkAgent = mNetworkAgent;
- mNetworkAgent = null;
Set<UidRange> oldUsers = mNetworkCapabilities.getUids();
// Configure the interface. Abort if any of these steps fails.
ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
try {
- updateState(DetailedState.CONNECTING, "establish");
String interfaze = jniGetName(tun.getFd());
// TEMP use the old jni calls until there is support for netd address setting
@@ -1037,15 +1072,26 @@
mConfig = config;
// Set up forwarding and DNS rules.
- agentConnect();
+ // First attempt to do a seamless handover that only changes the interface name and
+ // parameters. If that fails, disconnect.
+ if (oldConfig != null
+ && updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) {
+ // Keep mNetworkAgent unchanged
+ } else {
+ mNetworkAgent = null;
+ updateState(DetailedState.CONNECTING, "establish");
+ // Set up forwarding and DNS rules.
+ agentConnect();
+ // Remove the old tun's user forwarding rules
+ // The new tun's user rules have already been added above so they will take over
+ // as rules are deleted. This prevents data leakage as the rules are moved over.
+ agentDisconnect(oldNetworkAgent);
+ }
if (oldConnection != null) {
mContext.unbindService(oldConnection);
}
- // Remove the old tun's user forwarding rules
- // The new tun's user rules have already been added so they will take over
- // as rules are deleted. This prevents data leakage as the rules are moved over.
- agentDisconnect(oldNetworkAgent);
+
if (oldInterface != null && !oldInterface.equals(interfaze)) {
jniReset(oldInterface);
}