Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4c5c33e5 authored by Chad Brubaker's avatar Chad Brubaker
Browse files

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
parent 1b669231
Loading
Loading
Loading
Loading
+51 −26
Original line number Diff line number Diff line
@@ -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);

                // 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);


        // 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");
                        }
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
        // TODO: ensure that contract class eventually marks as connected
        updateState(DetailedState.AUTHENTICATING, "establish");
        return tun;