Merge "Use Vpn rules (not firewall) for always-on VPN" am: e4f56a7e44
am: cd8557d15a

Change-Id: I4e0aa1a88c259b77d7a9ac9e1dd02566e415005d
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 56fa420..469ddce 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3681,17 +3681,9 @@
             existing.shutdown();
         }
 
-        try {
-            if (tracker != null) {
-                mNetd.setFirewallEnabled(true);
-                mNetd.setFirewallInterfaceRule("lo", true);
-                mLockdownTracker = tracker;
-                mLockdownTracker.init();
-            } else {
-                mNetd.setFirewallEnabled(false);
-            }
-        } catch (RemoteException e) {
-            // ignored; NMS lives inside system_server
+        if (tracker != null) {
+            mLockdownTracker = tracker;
+            mLockdownTracker.init();
         }
     }
 
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 87938cb..f4e11c7 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -95,7 +95,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.NativeDaemonConnector.Command;
 import com.android.server.NativeDaemonConnector.SensitiveArg;
-import com.android.server.net.LockdownVpnTracker;
 import com.google.android.collect.Maps;
 
 import java.io.BufferedReader;
@@ -628,7 +627,7 @@
                 }
             }
 
-            setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
+            setFirewallEnabled(mFirewallEnabled);
 
             syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
             syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 584994a..5dc8640 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -264,6 +264,30 @@
     }
 
     /**
+     * Chooses whether to force all connections to go though VPN.
+     *
+     * Used to enable/disable legacy VPN lockdown.
+     *
+     * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
+     * previous settings from calling that function will be replaced and saved with the
+     * always-on state.
+     *
+     * @param lockdown whether to prevent all traffic outside of a VPN.
+     */
+    public synchronized void setLockdown(boolean lockdown) {
+        enforceControlPermissionOrInternalCaller();
+
+        setVpnForcedLocked(lockdown);
+        mLockdown = lockdown;
+
+        // Update app lockdown setting if it changed. Legacy VPN lockdown status is controlled by
+        // LockdownVpnTracker.isEnabled() which keeps track of its own state.
+        if (mAlwaysOn) {
+            saveAlwaysOnPackage();
+        }
+    }
+
+    /**
      * Configures an always-on VPN connection through a specific application.
      * This connection is automatically granted and persisted after a reboot.
      *
@@ -376,7 +400,7 @@
             mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
                     getAlwaysOnPackage(), mUserHandle);
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
-                    (mLockdown ? 1 : 0), mUserHandle);
+                    (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -557,6 +581,7 @@
             mConfig = null;
 
             updateState(DetailedState.IDLE, "prepare");
+            setVpnForcedLocked(mLockdown);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -1003,9 +1028,7 @@
                         Log.wtf(TAG, "Failed to add restricted user to owner", e);
                     }
                 }
-                if (mAlwaysOn) {
-                    setVpnForcedLocked(mLockdown);
-                }
+                setVpnForcedLocked(mLockdown);
             }
         }
     }
@@ -1022,9 +1045,7 @@
                         Log.wtf(TAG, "Failed to remove restricted user to owner", e);
                     }
                 }
-                if (mAlwaysOn) {
-                    setVpnForcedLocked(mLockdown);
-                }
+                setVpnForcedLocked(mLockdown);
             }
         }
     }
@@ -1034,7 +1055,7 @@
      */
     public synchronized void onUserStopped() {
         // Switch off networking lockdown (if it was enabled)
-        setVpnForcedLocked(false);
+        setLockdown(false);
         mAlwaysOn = false;
 
         unregisterPackageChangeReceiverLocked();
@@ -1061,20 +1082,31 @@
      */
     @GuardedBy("this")
     private void setVpnForcedLocked(boolean enforce) {
+        final List<String> exemptedPackages =
+                isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
+        setVpnForcedWithExemptionsLocked(enforce, exemptedPackages);
+    }
+
+    /**
+     * @see #setVpnForcedLocked
+     */
+    @GuardedBy("this")
+    private void setVpnForcedWithExemptionsLocked(boolean enforce,
+            @Nullable List<String> exemptedPackages) {
         final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
+
+        Set<UidRange> addedRanges = Collections.emptySet();
         if (enforce) {
-            final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
+            addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
                     /* allowedApplications */ null,
-                    /* disallowedApplications */ Collections.singletonList(mPackage));
+                    /* disallowedApplications */ exemptedPackages);
 
             removedRanges.removeAll(addedRanges);
             addedRanges.removeAll(mBlockedUsers);
-
-            setAllowOnlyVpnForUids(false, removedRanges);
-            setAllowOnlyVpnForUids(true, addedRanges);
-        } else {
-            setAllowOnlyVpnForUids(false, removedRanges);
         }
+
+        setAllowOnlyVpnForUids(false, removedRanges);
+        setAllowOnlyVpnForUids(true, addedRanges);
     }
 
     /**
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 4a8539a..b40a7e5 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -139,7 +139,6 @@
                 " " + mAcceptedEgressIface + "->" + egressIface);
 
         if (egressDisconnected || egressChanged) {
-            clearSourceRulesLocked();
             mAcceptedEgressIface = null;
             mVpn.stopLegacyVpnPrivileged();
         }
@@ -191,24 +190,6 @@
             EventLogTags.writeLockdownVpnConnected(egressType);
             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
 
-            try {
-                clearSourceRulesLocked();
-
-                mNetService.setFirewallInterfaceRule(iface, true);
-                for (LinkAddress addr : sourceAddrs) {
-                    setFirewallEgressSourceRule(addr, true);
-                }
-
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);
-
-                mErrorCount = 0;
-                mAcceptedIface = iface;
-                mAcceptedSourceAddr = sourceAddrs;
-            } catch (RemoteException e) {
-                throw new RuntimeException("Problem setting firewall rules", e);
-            }
-
             final NetworkInfo clone = new NetworkInfo(egressInfo);
             augmentNetworkInfo(clone);
             mConnService.sendConnectedBroadcast(clone);
@@ -225,19 +206,11 @@
         Slog.d(TAG, "initLocked()");
 
         mVpn.setEnableTeardown(false);
+        mVpn.setLockdown(true);
 
         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
         mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
 
-        try {
-            // TODO: support non-standard port numbers
-            mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Problem setting firewall rules", e);
-        }
-
         handleStateChangedLocked();
     }
 
@@ -254,14 +227,7 @@
         mErrorCount = 0;
 
         mVpn.stopLegacyVpnPrivileged();
-        try {
-            mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Problem setting firewall rules", e);
-        }
-        clearSourceRulesLocked();
+        mVpn.setLockdown(false);
         hideNotification();
 
         mContext.unregisterReceiver(mResetReceiver);
@@ -278,35 +244,6 @@
         }
     }
 
-    private void clearSourceRulesLocked() {
-        try {
-            if (mAcceptedIface != null) {
-                mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
-                mAcceptedIface = null;
-            }
-            if (mAcceptedSourceAddr != null) {
-                for (LinkAddress addr : mAcceptedSourceAddr) {
-                    setFirewallEgressSourceRule(addr, false);
-                }
-
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);
-
-                mAcceptedSourceAddr = null;
-            }
-        } catch (RemoteException e) {
-            throw new RuntimeException("Problem setting firewall rules", e);
-        }
-    }
-
-    private void setFirewallEgressSourceRule(
-            LinkAddress address, boolean allow) throws RemoteException {
-        // Our source address based firewall rules must only cover our own source address, not the
-        // whole subnet
-        final String addrString = address.getAddress().getHostAddress();
-        mNetService.setFirewallEgressSourceRule(addrString, allow);
-    }
-
     public void onNetworkInfoChanged() {
         synchronized (mStateLock) {
             handleStateChangedLocked();
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index efe6fec..506d9e5 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,6 +27,7 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -42,6 +43,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.net.VpnConfig;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Map;
@@ -101,8 +104,10 @@
     @Override
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         setMockedPackages(mPackages);
+
         when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
@@ -258,6 +263,58 @@
     }
 
     @SmallTest
+    public void testLockdownRuleRepeatability() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        // Given legacy lockdown is already enabled,
+        vpn.setLockdown(true);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+                eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+        // Enabling legacy lockdown twice should do nothing.
+        vpn.setLockdown(true);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+
+        // And disabling should remove the rules exactly once.
+        vpn.setLockdown(false);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+                eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+        // Removing the lockdown again should have no effect.
+        vpn.setLockdown(false);
+        verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+    }
+
+    @SmallTest
+    public void testLockdownRuleReversibility() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        final UidRange[] entireUser = {
+            UidRange.createForUser(primaryUser.id)
+        };
+        final UidRange[] exceptPkg0 = {
+            new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
+            new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+        };
+
+        final InOrder order = inOrder(mNetService);
+
+        // Given lockdown is enabled with no package (legacy VPN),
+        vpn.setLockdown(true);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+
+        // When a new VPN package is set the rules should change to cover that package.
+        vpn.prepare(null, PKGS[0]);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));
+
+        // When that VPN package is unset, everything should be undone again in reverse.
+        vpn.prepare(null, VpnConfig.LEGACY_VPN);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+    }
+
+    @SmallTest
     public void testNotificationShownForAlwaysOnApp() {
         final UserHandle userHandle = UserHandle.of(primaryUser.id);
         final Vpn vpn = createVpn(primaryUser.id);