From 4c5c33e5e6fb37f85977c70a0baba4e1ed51fe0d Mon Sep 17 00:00:00 2001 From: Chad Brubaker Date: Fri, 21 Feb 2014 11:04:45 -0800 Subject: [PATCH] Fix support for simultaneous VPN tuns A VPN can once again bring up a new tun interface while the old tun is running. Once the new tun is set up the routing rules will be removed from the old tun. It is up to the application to drain the old tun of traffic and close it. If the new tun fails to come up the old tun will remain untouched and can still be used. To prevent leakage the new rules are added before the old tun is shutdown. Netd/Dns has been changed to allow multiple rules to exist at once with the most recently added rule taking priority. Bug: 12134439 Change-Id: I7e00c7c68cc339d81f09b3d2a33276ffc76985f5 --- .../com/android/server/connectivity/Vpn.java | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 03405e7c0cbcf..8f25860172dea 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -352,6 +352,12 @@ public class Vpn extends BaseNetworkStateTracker { Binder.restoreCallingIdentity(token); } + // Save the old config in case we need to go back. + VpnConfig oldConfig = mConfig; + String oldInterface = mInterface; + Connection oldConnection = mConnection; + SparseBooleanArray oldUsers = mVpnUsers; + // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); try { @@ -371,12 +377,7 @@ public class Vpn extends BaseNetworkStateTracker { new UserHandle(mUserId))) { throw new IllegalStateException("Cannot bind " + config.user); } - if (mConnection != null) { - mContext.unbindService(mConnection); - } - if (mInterface != null && !mInterface.equals(interfaze)) { - jniReset(mInterface); - } + mConnection = connection; mInterface = interfaze; @@ -385,50 +386,74 @@ public class Vpn extends BaseNetworkStateTracker { config.interfaze = mInterface; config.startTime = SystemClock.elapsedRealtime(); mConfig = config; + // Set up forwarding and DNS rules. mVpnUsers = new SparseBooleanArray(); token = Binder.clearCallingIdentity(); try { mCallback.setMarkedForwarding(mInterface); - mCallback.setRoutes(interfaze, config.routes); + mCallback.setRoutes(mInterface, config.routes); mCallback.override(mInterface, config.dnsServers, config.searchDomains); addVpnUserLocked(mUserId); - - } finally { - Binder.restoreCallingIdentity(token); - } - - } catch (RuntimeException e) { - updateState(DetailedState.FAILED, "establish"); - IoUtils.closeQuietly(tun); - // make sure marked forwarding is cleared if it was set - try { - mCallback.clearMarkedForwarding(mInterface); - } catch (Exception ingored) { - // ignored - } - throw e; - } - Log.i(TAG, "Established by " + config.user + " on " + mInterface); - - - // If we are owner assign all Restricted Users to this VPN - if (mUserId == UserHandle.USER_OWNER) { - token = Binder.clearCallingIdentity(); - try { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); + // If we are owner assign all Restricted Users to this VPN + if (mUserId == UserHandle.USER_OWNER) { + for (UserInfo user : mgr.getUsers()) { + if (user.isRestricted()) { + try { + addVpnUserLocked(user.id); + } catch (Exception e) { + Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); + } } } } } finally { Binder.restoreCallingIdentity(token); } + + if (oldConnection != null) { + mContext.unbindService(oldConnection); + } + if (oldInterface != null && !oldInterface.equals(interfaze)) { + // 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. + token = Binder.clearCallingIdentity(); + try { + final int size = oldUsers.size(); + final boolean forwardDns = (oldConfig.dnsServers != null && + oldConfig.dnsServers.size() != 0); + for (int i = 0; i < size; i++) { + int user = oldUsers.keyAt(i); + mCallback.clearUserForwarding(oldInterface, user, forwardDns); + } + mCallback.clearMarkedForwarding(oldInterface); + } finally { + Binder.restoreCallingIdentity(token); + } + jniReset(oldInterface); + } + } catch (RuntimeException e) { + updateState(DetailedState.FAILED, "establish"); + IoUtils.closeQuietly(tun); + // make sure marked forwarding is cleared if it was set + token = Binder.clearCallingIdentity(); + try { + mCallback.clearMarkedForwarding(mInterface); + } catch (Exception ingored) { + // ignored + } finally { + Binder.restoreCallingIdentity(token); + } + // restore old state + mConfig = oldConfig; + mConnection = oldConnection; + mVpnUsers = oldUsers; + mInterface = oldInterface; + throw e; } + Log.i(TAG, "Established by " + config.user + " on " + mInterface); + // TODO: ensure that contract class eventually marks as connected updateState(DetailedState.AUTHENTICATING, "establish"); return tun;