Always-on VPN.

Adds support for always-on VPN profiles, also called "lockdown." When
enabled, LockdownVpnTracker manages the netd firewall to prevent
unencrypted traffic from leaving the device. It creates narrow rules
to only allow traffic to the selected VPN server. When an egress
network becomes available, LockdownVpnTracker will try bringing up
the VPN connection, and will reconnect if disconnected.

ConnectivityService augments any NetworkInfo based on the lockdown
VPN status to help apps wait until the VPN is connected.

This feature requires that VPN profiles use an IP address for both
VPN server and DNS. It also blocks non-default APN access when
enabled. Waits for USER_PRESENT after boot to check KeyStore status.

Bug: 5756357
Change-Id: If615f206b1634000d78a8350a17e88bfcac8e0d0
This commit is contained in:
Jeff Sharkey
2012-08-25 00:05:46 -07:00
parent 64d8b3be3a
commit ebcc7978c1
3 changed files with 135 additions and 7 deletions

View File

@@ -912,4 +912,13 @@ public class ConnectivityManager {
return false; return false;
} }
} }
/** {@hide} */
public boolean updateLockdownVpn() {
try {
return mService.updateLockdownVpn();
} catch (RemoteException e) {
return false;
}
}
} }

View File

@@ -122,4 +122,6 @@ interface IConnectivityManager
void startLegacyVpn(in VpnProfile profile); void startLegacyVpn(in VpnProfile profile);
LegacyVpnInfo getLegacyVpnInfo(); LegacyVpnInfo getLegacyVpnInfo();
boolean updateLockdownVpn();
} }

View File

@@ -16,6 +16,7 @@
package com.android.server; package com.android.server;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -31,13 +32,13 @@ import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker; import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.BroadcastReceiver;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.ContentObserver; import android.database.ContentObserver;
@@ -80,6 +81,7 @@ import android.os.ServiceManager;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.provider.Settings; import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore; import android.security.KeyStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.EventLog; import android.util.EventLog;
@@ -91,11 +93,11 @@ import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile; import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.Phone; import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Preconditions;
import com.android.server.am.BatteryStatsService; import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn; import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver; import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Lists; import com.google.android.collect.Lists;
import com.google.android.collect.Sets; import com.google.android.collect.Sets;
@@ -142,11 +144,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private Tethering mTethering; private Tethering mTethering;
private boolean mTetheringConfigValid = false; private boolean mTetheringConfigValid = false;
private final KeyStore mKeyStore; private KeyStore mKeyStore;
private Vpn mVpn; private Vpn mVpn;
private VpnCallback mVpnCallback = new VpnCallback(); private VpnCallback mVpnCallback = new VpnCallback();
private boolean mLockdownEnabled;
private LockdownVpnTracker mLockdownTracker;
/** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */ /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
private Object mRulesLock = new Object(); private Object mRulesLock = new Object();
/** Currently active network rules by UID. */ /** Currently active network rules by UID. */
@@ -276,6 +281,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
private static final int EVENT_SET_POLICY_DATA_ENABLE = 13; private static final int EVENT_SET_POLICY_DATA_ENABLE = 13;
private static final int EVENT_VPN_STATE_CHANGED = 14;
/** Handler used for internal events. */ /** Handler used for internal events. */
private InternalHandler mHandler; private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */ /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -786,6 +793,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
info = new NetworkInfo(info); info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null); info.setDetailedState(DetailedState.BLOCKED, null, null);
} }
if (mLockdownTracker != null) {
info = mLockdownTracker.augmentNetworkInfo(info);
}
return info; return info;
} }
@@ -803,6 +813,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return getNetworkInfo(mActiveDefaultNetwork, uid); return getNetworkInfo(mActiveDefaultNetwork, uid);
} }
public NetworkInfo getActiveNetworkInfoUnfiltered() {
enforceAccessPermission();
if (isNetworkTypeValid(mActiveDefaultNetwork)) {
final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
if (tracker != null) {
return tracker.getNetworkInfo();
}
}
return null;
}
@Override @Override
public NetworkInfo getActiveNetworkInfoForUid(int uid) { public NetworkInfo getActiveNetworkInfoForUid(int uid) {
enforceConnectivityInternalPermission(); enforceConnectivityInternalPermission();
@@ -1062,6 +1083,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// TODO - move this into individual networktrackers // TODO - move this into individual networktrackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature); int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
if (mLockdownEnabled) {
// Since carrier APNs usually aren't available from VPN
// endpoint, mark them as unavailable.
return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
}
if (mProtectedNetworks.contains(usedNetworkType)) { if (mProtectedNetworks.contains(usedNetworkType)) {
enforceConnectivityInternalPermission(); enforceConnectivityInternalPermission();
} }
@@ -1769,7 +1796,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} }
} }
private void sendConnectedBroadcast(NetworkInfo info) { public void sendConnectedBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE); sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION); sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
} }
@@ -1784,6 +1811,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} }
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
if (mLockdownTracker != null) {
info = mLockdownTracker.augmentNetworkInfo(info);
}
Intent intent = new Intent(bcastType); Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -1915,8 +1946,26 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} }
// load the global proxy at startup // load the global proxy at startup
mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY)); mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
// Try bringing up tracker, but if KeyStore isn't ready yet, wait
// for user to unlock device.
if (!updateLockdownVpn()) {
final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(mUserPresentReceiver, filter);
}
} }
private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Try creating lockdown tracker, since user present usually means
// unlocked keystore.
if (updateLockdownVpn()) {
mContext.unregisterReceiver(this);
}
}
};
private void handleConnect(NetworkInfo info) { private void handleConnect(NetworkInfo info) {
final int type = info.getType(); final int type = info.getType();
@@ -2595,6 +2644,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} else if (state == NetworkInfo.State.CONNECTED) { } else if (state == NetworkInfo.State.CONNECTED) {
handleConnect(info); handleConnect(info);
} }
if (mLockdownTracker != null) {
mLockdownTracker.onNetworkInfoChanged(info);
}
break; break;
case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
info = (NetworkInfo) msg.obj; info = (NetworkInfo) msg.obj;
@@ -2692,6 +2744,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
final int networkType = msg.arg1; final int networkType = msg.arg1;
final boolean enabled = msg.arg2 == ENABLED; final boolean enabled = msg.arg2 == ENABLED;
handleSetPolicyDataEnable(networkType, enabled); handleSetPolicyDataEnable(networkType, enabled);
break;
}
case EVENT_VPN_STATE_CHANGED: {
if (mLockdownTracker != null) {
mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
}
break;
} }
} }
} }
@@ -3090,6 +3149,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
@Override @Override
public boolean protectVpn(ParcelFileDescriptor socket) { public boolean protectVpn(ParcelFileDescriptor socket) {
throwIfLockdownEnabled();
try { try {
int type = mActiveDefaultNetwork; int type = mActiveDefaultNetwork;
if (ConnectivityManager.isNetworkTypeValid(type)) { if (ConnectivityManager.isNetworkTypeValid(type)) {
@@ -3116,6 +3176,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
@Override @Override
public boolean prepareVpn(String oldPackage, String newPackage) { public boolean prepareVpn(String oldPackage, String newPackage) {
throwIfLockdownEnabled();
return mVpn.prepare(oldPackage, newPackage); return mVpn.prepare(oldPackage, newPackage);
} }
@@ -3128,6 +3189,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
@Override @Override
public ParcelFileDescriptor establishVpn(VpnConfig config) { public ParcelFileDescriptor establishVpn(VpnConfig config) {
throwIfLockdownEnabled();
return mVpn.establish(config); return mVpn.establish(config);
} }
@@ -3137,6 +3199,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
@Override @Override
public void startLegacyVpn(VpnProfile profile) { public void startLegacyVpn(VpnProfile profile) {
throwIfLockdownEnabled();
final LinkProperties egress = getActiveLinkProperties(); final LinkProperties egress = getActiveLinkProperties();
if (egress == null) { if (egress == null) {
throw new IllegalStateException("Missing active network connection"); throw new IllegalStateException("Missing active network connection");
@@ -3152,6 +3215,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/ */
@Override @Override
public LegacyVpnInfo getLegacyVpnInfo() { public LegacyVpnInfo getLegacyVpnInfo() {
throwIfLockdownEnabled();
return mVpn.getLegacyVpnInfo(); return mVpn.getLegacyVpnInfo();
} }
@@ -3170,8 +3234,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} }
public void onStateChanged(NetworkInfo info) { public void onStateChanged(NetworkInfo info) {
// TODO: if connected, release delayed broadcast mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
// TODO: if disconnected, consider kicking off reconnect
} }
public void override(List<String> dnsServers, List<String> searchDomains) { public void override(List<String> dnsServers, List<String> searchDomains) {
@@ -3240,4 +3303,58 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} }
} }
} }
@Override
public boolean updateLockdownVpn() {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
// Tear down existing lockdown if profile was removed
mLockdownEnabled = LockdownVpnTracker.isEnabled();
if (mLockdownEnabled) {
if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
return false;
}
final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
final VpnProfile profile = VpnProfile.decode(
profileName, mKeyStore.get(Credentials.VPN + profileName));
setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile));
} else {
setLockdownTracker(null);
}
return true;
}
/**
* Internally set new {@link LockdownVpnTracker}, shutting down any existing
* {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
*/
private void setLockdownTracker(LockdownVpnTracker tracker) {
// Shutdown any existing tracker
final LockdownVpnTracker existing = mLockdownTracker;
mLockdownTracker = null;
if (existing != null) {
existing.shutdown();
}
try {
if (tracker != null) {
mNetd.setFirewallEnabled(true);
mLockdownTracker = tracker;
mLockdownTracker.init();
} else {
mNetd.setFirewallEnabled(false);
}
} catch (RemoteException e) {
// ignored; NMS lives inside system_server
}
}
private void throwIfLockdownEnabled() {
if (mLockdownEnabled) {
throw new IllegalStateException("Unavailable in lockdown mode");
}
}
} }