LockdownVpnTrackerpublic class LockdownVpnTracker extends Object State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
connected and kicks off VPN connection, managing any required {@code netd}
firewall rules. |
Fields Summary |
---|
private static final String | TAG | private static final int | MAX_ERROR_COUNTNumber of VPN attempts before waiting for user intervention. | private static final String | ACTION_LOCKDOWN_RESET | private static final String | ACTION_VPN_SETTINGS | private static final String | EXTRA_PICK_LOCKDOWN | private static final int | ROOT_UID | private final android.content.Context | mContext | private final android.os.INetworkManagementService | mNetService | private final com.android.server.ConnectivityService | mConnService | private final com.android.server.connectivity.Vpn | mVpn | private final com.android.internal.net.VpnProfile | mProfile | private final Object | mStateLock | private final android.app.PendingIntent | mConfigIntent | private final android.app.PendingIntent | mResetIntent | private String | mAcceptedEgressIface | private String | mAcceptedIface | private List | mAcceptedSourceAddr | private int | mErrorCount | private android.content.BroadcastReceiver | mResetReceiver |
Constructors Summary |
---|
public LockdownVpnTracker(android.content.Context context, android.os.INetworkManagementService netService, com.android.server.ConnectivityService connService, com.android.server.connectivity.Vpn vpn, com.android.internal.net.VpnProfile profile)
mContext = Preconditions.checkNotNull(context);
mNetService = Preconditions.checkNotNull(netService);
mConnService = Preconditions.checkNotNull(connService);
mVpn = Preconditions.checkNotNull(vpn);
mProfile = Preconditions.checkNotNull(profile);
final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
|
Methods Summary |
---|
public android.net.NetworkInfo | augmentNetworkInfo(android.net.NetworkInfo info)
if (info.isConnected()) {
final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
info = new NetworkInfo(info);
info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
}
return info;
| 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(ROOT_UID, false);
mNetService.setFirewallUidRule(Os.getuid(), false);
mAcceptedSourceAddr = null;
}
} catch (RemoteException e) {
throw new RuntimeException("Problem setting firewall rules", e);
}
| private void | handleStateChangedLocked()Watch for state changes to both active egress network, kicking off a VPN
connection when ready, or setting firewall rules once VPN is connected.
final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
final LinkProperties egressProp = mConnService.getActiveLinkProperties();
final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
// Restart VPN when egress network disconnected or changed
final boolean egressDisconnected = egressInfo == null
|| State.DISCONNECTED.equals(egressInfo.getState());
final boolean egressChanged = egressProp == null
|| !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
final String egressTypeName = (egressInfo == null) ?
null : ConnectivityManager.getNetworkTypeName(egressInfo.getType());
final String egressIface = (egressProp == null) ?
null : egressProp.getInterfaceName();
Slog.d(TAG, "handleStateChanged: egress=" + egressTypeName +
" " + mAcceptedEgressIface + "->" + egressIface);
if (egressDisconnected || egressChanged) {
clearSourceRulesLocked();
mAcceptedEgressIface = null;
mVpn.stopLegacyVpnPrivileged();
}
if (egressDisconnected) {
hideNotification();
return;
}
final int egressType = egressInfo.getType();
if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
EventLogTags.writeLockdownVpnError(egressType);
}
if (mErrorCount > MAX_ERROR_COUNT) {
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
} else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
if (mProfile.isValidLockdownProfile()) {
Slog.d(TAG, "Active network connected; starting VPN");
EventLogTags.writeLockdownVpnConnecting(egressType);
showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
mAcceptedEgressIface = egressProp.getInterfaceName();
try {
// Use the privileged method because Lockdown VPN is initiated by the system, so
// no additional permission checks are necessary.
mVpn.startLegacyVpnPrivileged(mProfile, KeyStore.getInstance(), egressProp);
} catch (IllegalStateException e) {
mAcceptedEgressIface = null;
Slog.e(TAG, "Failed to start VPN", e);
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
}
} else {
Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
}
} else if (vpnInfo.isConnected() && vpnConfig != null) {
final String iface = vpnConfig.interfaze;
final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
if (TextUtils.equals(iface, mAcceptedIface)
&& sourceAddrs.equals(mAcceptedSourceAddr)) {
return;
}
Slog.d(TAG, "VPN connected using iface=" + iface +
", sourceAddr=" + sourceAddrs.toString());
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(ROOT_UID, true);
mNetService.setFirewallUidRule(Os.getuid(), true);
mErrorCount = 0;
mAcceptedIface = iface;
mAcceptedSourceAddr = sourceAddrs;
} catch (RemoteException e) {
throw new RuntimeException("Problem setting firewall rules", e);
}
mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
}
| private void | hideNotification()
NotificationManager.from(mContext).cancel(TAG, 0);
| public void | init()
synchronized (mStateLock) {
initLocked();
}
| private void | initLocked()
Slog.d(TAG, "initLocked()");
mVpn.setEnableTeardown(false);
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);
}
synchronized (mStateLock) {
handleStateChangedLocked();
}
| public static boolean | isEnabled()
return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
| public void | onNetworkInfoChanged()
synchronized (mStateLock) {
handleStateChangedLocked();
}
| public void | onVpnStateChanged(android.net.NetworkInfo info)
if (info.getDetailedState() == DetailedState.FAILED) {
mErrorCount++;
}
synchronized (mStateLock) {
handleStateChangedLocked();
}
| public void | reset()
Slog.d(TAG, "reset()");
synchronized (mStateLock) {
// cycle tracker, reset error count, and trigger retry
shutdownLocked();
initLocked();
handleStateChangedLocked();
}
| private void | setFirewallEgressSourceRule(android.net.LinkAddress address, boolean allow)
// 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);
| private void | showNotification(int titleRes, int iconRes)
final Notification.Builder builder = new Notification.Builder(mContext)
.setWhen(0)
.setSmallIcon(iconRes)
.setContentTitle(mContext.getString(titleRes))
.setContentText(mContext.getString(R.string.vpn_lockdown_config))
.setContentIntent(mConfigIntent)
.setPriority(Notification.PRIORITY_LOW)
.setOngoing(true)
.addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
mResetIntent)
.setColor(mContext.getResources().getColor(
com.android.internal.R.color.system_notification_accent_color));
NotificationManager.from(mContext).notify(TAG, 0, builder.build());
| public void | shutdown()
synchronized (mStateLock) {
shutdownLocked();
}
| private void | shutdownLocked()
Slog.d(TAG, "shutdownLocked()");
mAcceptedEgressIface = null;
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();
hideNotification();
mContext.unregisterReceiver(mResetReceiver);
mVpn.setEnableTeardown(true);
|
|