Merge "Use Vpn rules (not firewall) for always-on VPN" am: e4f56a7e44 am: cd8557d15a
am: 4de3794c26
Change-Id: Ic1e96eac68ddc8a713b3c71b5c126cb8b99c59f5
This commit is contained in:
@@ -3557,17 +3557,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
existing.shutdown();
|
existing.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (tracker != null) {
|
||||||
if (tracker != null) {
|
mLockdownTracker = tracker;
|
||||||
mNetd.setFirewallEnabled(true);
|
mLockdownTracker.init();
|
||||||
mNetd.setFirewallInterfaceRule("lo", true);
|
|
||||||
mLockdownTracker = tracker;
|
|
||||||
mLockdownTracker.init();
|
|
||||||
} else {
|
|
||||||
mNetd.setFirewallEnabled(false);
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
// ignored; NMS lives inside system_server
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ import com.android.internal.util.HexDump;
|
|||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
import com.android.server.NativeDaemonConnector.Command;
|
import com.android.server.NativeDaemonConnector.Command;
|
||||||
import com.android.server.NativeDaemonConnector.SensitiveArg;
|
import com.android.server.NativeDaemonConnector.SensitiveArg;
|
||||||
import com.android.server.net.LockdownVpnTracker;
|
|
||||||
import com.google.android.collect.Maps;
|
import com.google.android.collect.Maps;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@@ -660,7 +659,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
|
setFirewallEnabled(mFirewallEnabled);
|
||||||
|
|
||||||
syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
|
syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
|
||||||
syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
|
syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
|
||||||
|
|||||||
@@ -271,6 +271,30 @@ public class Vpn {
|
|||||||
updateAlwaysOnNotification(detailedState);
|
updateAlwaysOnNotification(detailedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Configures an always-on VPN connection through a specific application.
|
||||||
* This connection is automatically granted and persisted after a reboot.
|
* This connection is automatically granted and persisted after a reboot.
|
||||||
@@ -384,7 +408,7 @@ public class Vpn {
|
|||||||
mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
|
mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
|
||||||
getAlwaysOnPackage(), mUserHandle);
|
getAlwaysOnPackage(), mUserHandle);
|
||||||
mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
|
mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
|
||||||
(mLockdown ? 1 : 0), mUserHandle);
|
(mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(token);
|
Binder.restoreCallingIdentity(token);
|
||||||
}
|
}
|
||||||
@@ -577,6 +601,7 @@ public class Vpn {
|
|||||||
mConfig = null;
|
mConfig = null;
|
||||||
|
|
||||||
updateState(DetailedState.IDLE, "prepare");
|
updateState(DetailedState.IDLE, "prepare");
|
||||||
|
setVpnForcedLocked(mLockdown);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(token);
|
Binder.restoreCallingIdentity(token);
|
||||||
}
|
}
|
||||||
@@ -1023,9 +1048,7 @@ public class Vpn {
|
|||||||
Log.wtf(TAG, "Failed to add restricted user to owner", e);
|
Log.wtf(TAG, "Failed to add restricted user to owner", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mAlwaysOn) {
|
setVpnForcedLocked(mLockdown);
|
||||||
setVpnForcedLocked(mLockdown);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1042,9 +1065,7 @@ public class Vpn {
|
|||||||
Log.wtf(TAG, "Failed to remove restricted user to owner", e);
|
Log.wtf(TAG, "Failed to remove restricted user to owner", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mAlwaysOn) {
|
setVpnForcedLocked(mLockdown);
|
||||||
setVpnForcedLocked(mLockdown);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1054,7 +1075,7 @@ public class Vpn {
|
|||||||
*/
|
*/
|
||||||
public synchronized void onUserStopped() {
|
public synchronized void onUserStopped() {
|
||||||
// Switch off networking lockdown (if it was enabled)
|
// Switch off networking lockdown (if it was enabled)
|
||||||
setVpnForcedLocked(false);
|
setLockdown(false);
|
||||||
mAlwaysOn = false;
|
mAlwaysOn = false;
|
||||||
|
|
||||||
unregisterPackageChangeReceiverLocked();
|
unregisterPackageChangeReceiverLocked();
|
||||||
@@ -1081,20 +1102,31 @@ public class Vpn {
|
|||||||
*/
|
*/
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private void setVpnForcedLocked(boolean enforce) {
|
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);
|
final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
|
||||||
|
|
||||||
|
Set<UidRange> addedRanges = Collections.emptySet();
|
||||||
if (enforce) {
|
if (enforce) {
|
||||||
final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
|
addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
|
||||||
/* allowedApplications */ null,
|
/* allowedApplications */ null,
|
||||||
/* disallowedApplications */ Collections.singletonList(mPackage));
|
/* disallowedApplications */ exemptedPackages);
|
||||||
|
|
||||||
removedRanges.removeAll(addedRanges);
|
removedRanges.removeAll(addedRanges);
|
||||||
addedRanges.removeAll(mBlockedUsers);
|
addedRanges.removeAll(mBlockedUsers);
|
||||||
|
|
||||||
setAllowOnlyVpnForUids(false, removedRanges);
|
|
||||||
setAllowOnlyVpnForUids(true, addedRanges);
|
|
||||||
} else {
|
|
||||||
setAllowOnlyVpnForUids(false, removedRanges);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAllowOnlyVpnForUids(false, removedRanges);
|
||||||
|
setAllowOnlyVpnForUids(true, addedRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ public class LockdownVpnTracker {
|
|||||||
" " + mAcceptedEgressIface + "->" + egressIface);
|
" " + mAcceptedEgressIface + "->" + egressIface);
|
||||||
|
|
||||||
if (egressDisconnected || egressChanged) {
|
if (egressDisconnected || egressChanged) {
|
||||||
clearSourceRulesLocked();
|
|
||||||
mAcceptedEgressIface = null;
|
mAcceptedEgressIface = null;
|
||||||
mVpn.stopLegacyVpnPrivileged();
|
mVpn.stopLegacyVpnPrivileged();
|
||||||
}
|
}
|
||||||
@@ -193,24 +192,6 @@ public class LockdownVpnTracker {
|
|||||||
EventLogTags.writeLockdownVpnConnected(egressType);
|
EventLogTags.writeLockdownVpnConnected(egressType);
|
||||||
showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
|
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);
|
final NetworkInfo clone = new NetworkInfo(egressInfo);
|
||||||
augmentNetworkInfo(clone);
|
augmentNetworkInfo(clone);
|
||||||
mConnService.sendConnectedBroadcast(clone);
|
mConnService.sendConnectedBroadcast(clone);
|
||||||
@@ -227,19 +208,11 @@ public class LockdownVpnTracker {
|
|||||||
Slog.d(TAG, "initLocked()");
|
Slog.d(TAG, "initLocked()");
|
||||||
|
|
||||||
mVpn.setEnableTeardown(false);
|
mVpn.setEnableTeardown(false);
|
||||||
|
mVpn.setLockdown(true);
|
||||||
|
|
||||||
final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
|
final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
|
||||||
mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
|
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();
|
handleStateChangedLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,14 +229,7 @@ public class LockdownVpnTracker {
|
|||||||
mErrorCount = 0;
|
mErrorCount = 0;
|
||||||
|
|
||||||
mVpn.stopLegacyVpnPrivileged();
|
mVpn.stopLegacyVpnPrivileged();
|
||||||
try {
|
mVpn.setLockdown(false);
|
||||||
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();
|
|
||||||
hideNotification();
|
hideNotification();
|
||||||
|
|
||||||
mContext.unregisterReceiver(mResetReceiver);
|
mContext.unregisterReceiver(mResetReceiver);
|
||||||
@@ -280,35 +246,6 @@ public class LockdownVpnTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
public void onNetworkInfoChanged() {
|
||||||
synchronized (mStateLock) {
|
synchronized (mStateLock) {
|
||||||
handleStateChangedLocked();
|
handleStateChangedLocked();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.annotation.UserIdInt;
|
|||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
@@ -42,6 +43,8 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
import com.android.internal.net.VpnConfig;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -101,8 +104,10 @@ public class VpnTest extends AndroidTestCase {
|
|||||||
@Override
|
@Override
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||||
setMockedPackages(mPackages);
|
setMockedPackages(mPackages);
|
||||||
|
|
||||||
when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
|
when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
|
||||||
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
|
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
|
||||||
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
|
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
|
||||||
@@ -257,6 +262,58 @@ public class VpnTest extends AndroidTestCase {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@SmallTest
|
||||||
public void testNotificationShownForAlwaysOnApp() {
|
public void testNotificationShownForAlwaysOnApp() {
|
||||||
final UserHandle userHandle = UserHandle.of(primaryUser.id);
|
final UserHandle userHandle = UserHandle.of(primaryUser.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user