LockdownVpnTracker.java revision 69ddab4575ff684c533c995e07ca15fe18543fc0
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.net; 18 19import static android.Manifest.permission.CONNECTIVITY_INTERNAL; 20 21import android.app.Notification; 22import android.app.NotificationManager; 23import android.app.PendingIntent; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.net.LinkProperties; 29import android.net.NetworkInfo; 30import android.net.NetworkInfo.DetailedState; 31import android.net.NetworkInfo.State; 32import android.os.INetworkManagementService; 33import android.os.RemoteException; 34import android.security.Credentials; 35import android.security.KeyStore; 36import android.text.TextUtils; 37import android.util.Slog; 38 39import com.android.internal.R; 40import com.android.internal.net.VpnConfig; 41import com.android.internal.net.VpnProfile; 42import com.android.internal.util.Preconditions; 43import com.android.server.ConnectivityService; 44import com.android.server.connectivity.Vpn; 45 46/** 47 * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be 48 * connected and kicks off VPN connection, managing any required {@code netd} 49 * firewall rules. 50 */ 51public class LockdownVpnTracker { 52 private static final String TAG = "LockdownVpnTracker"; 53 54 /** Number of VPN attempts before waiting for user intervention. */ 55 private static final int MAX_ERROR_COUNT = 4; 56 57 private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; 58 59 private final Context mContext; 60 private final INetworkManagementService mNetService; 61 private final ConnectivityService mConnService; 62 private final Vpn mVpn; 63 private final VpnProfile mProfile; 64 65 private final Object mStateLock = new Object(); 66 67 private PendingIntent mResetIntent; 68 69 private String mAcceptedEgressIface; 70 private String mAcceptedIface; 71 private String mAcceptedSourceAddr; 72 73 private int mErrorCount; 74 75 public static boolean isEnabled() { 76 return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN); 77 } 78 79 public LockdownVpnTracker(Context context, INetworkManagementService netService, 80 ConnectivityService connService, Vpn vpn, VpnProfile profile) { 81 mContext = Preconditions.checkNotNull(context); 82 mNetService = Preconditions.checkNotNull(netService); 83 mConnService = Preconditions.checkNotNull(connService); 84 mVpn = Preconditions.checkNotNull(vpn); 85 mProfile = Preconditions.checkNotNull(profile); 86 87 final Intent intent = new Intent(ACTION_LOCKDOWN_RESET); 88 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 89 mResetIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 90 } 91 92 private BroadcastReceiver mResetReceiver = new BroadcastReceiver() { 93 @Override 94 public void onReceive(Context context, Intent intent) { 95 reset(); 96 } 97 }; 98 99 /** 100 * Watch for state changes to both active egress network, kicking off a VPN 101 * connection when ready, or setting firewall rules once VPN is connected. 102 */ 103 private void handleStateChangedLocked() { 104 Slog.d(TAG, "handleStateChanged()"); 105 106 final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered(); 107 final LinkProperties egressProp = mConnService.getActiveLinkProperties(); 108 109 final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); 110 final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); 111 112 // Restart VPN when egress network disconnected or changed 113 final boolean egressDisconnected = egressInfo == null 114 || State.DISCONNECTED.equals(egressInfo.getState()); 115 final boolean egressChanged = egressProp == null 116 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); 117 if (egressDisconnected || egressChanged) { 118 clearSourceRules(); 119 mAcceptedEgressIface = null; 120 mVpn.stopLegacyVpn(); 121 } 122 if (egressDisconnected) return; 123 124 if (mErrorCount > MAX_ERROR_COUNT) { 125 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); 126 127 } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) { 128 if (mProfile.isValidLockdownProfile()) { 129 Slog.d(TAG, "Active network connected; starting VPN"); 130 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); 131 132 mAcceptedEgressIface = egressProp.getInterfaceName(); 133 mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp); 134 135 } else { 136 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); 137 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); 138 } 139 140 } else if (vpnInfo.isConnected() && vpnConfig != null) { 141 final String iface = vpnConfig.interfaze; 142 final String sourceAddr = vpnConfig.addresses; 143 144 if (TextUtils.equals(iface, mAcceptedIface) 145 && TextUtils.equals(sourceAddr, mAcceptedSourceAddr)) { 146 return; 147 } 148 149 Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr); 150 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); 151 152 try { 153 clearSourceRules(); 154 155 mNetService.setFirewallInterfaceRule(iface, true); 156 mNetService.setFirewallEgressSourceRule(sourceAddr, true); 157 158 mErrorCount = 0; 159 mAcceptedIface = iface; 160 mAcceptedSourceAddr = sourceAddr; 161 } catch (RemoteException e) { 162 throw new RuntimeException("Problem setting firewall rules", e); 163 } 164 165 mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo)); 166 } 167 } 168 169 public void init() { 170 Slog.d(TAG, "init()"); 171 172 mVpn.setEnableNotifications(false); 173 174 final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET); 175 mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null); 176 177 try { 178 // TODO: support non-standard port numbers 179 mNetService.setFirewallEgressDestRule(mProfile.server, 500, true); 180 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true); 181 } catch (RemoteException e) { 182 throw new RuntimeException("Problem setting firewall rules", e); 183 } 184 185 synchronized (mStateLock) { 186 handleStateChangedLocked(); 187 } 188 } 189 190 public void shutdown() { 191 Slog.d(TAG, "shutdown()"); 192 193 mAcceptedEgressIface = null; 194 mErrorCount = 0; 195 196 mVpn.stopLegacyVpn(); 197 try { 198 mNetService.setFirewallEgressDestRule(mProfile.server, 500, false); 199 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false); 200 } catch (RemoteException e) { 201 throw new RuntimeException("Problem setting firewall rules", e); 202 } 203 clearSourceRules(); 204 hideNotification(); 205 206 mContext.unregisterReceiver(mResetReceiver); 207 mVpn.setEnableNotifications(true); 208 } 209 210 public void reset() { 211 // cycle tracker, reset error count, and trigger retry 212 shutdown(); 213 init(); 214 synchronized (mStateLock) { 215 handleStateChangedLocked(); 216 } 217 } 218 219 private void clearSourceRules() { 220 try { 221 if (mAcceptedIface != null) { 222 mNetService.setFirewallInterfaceRule(mAcceptedIface, false); 223 mAcceptedIface = null; 224 } 225 if (mAcceptedSourceAddr != null) { 226 mNetService.setFirewallEgressSourceRule(mAcceptedSourceAddr, false); 227 mAcceptedSourceAddr = null; 228 } 229 } catch (RemoteException e) { 230 throw new RuntimeException("Problem setting firewall rules", e); 231 } 232 } 233 234 public void onNetworkInfoChanged(NetworkInfo info) { 235 synchronized (mStateLock) { 236 handleStateChangedLocked(); 237 } 238 } 239 240 public void onVpnStateChanged(NetworkInfo info) { 241 if (info.getDetailedState() == DetailedState.FAILED) { 242 mErrorCount++; 243 } 244 synchronized (mStateLock) { 245 handleStateChangedLocked(); 246 } 247 } 248 249 public NetworkInfo augmentNetworkInfo(NetworkInfo info) { 250 final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); 251 info = new NetworkInfo(info); 252 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); 253 return info; 254 } 255 256 private void showNotification(int titleRes, int iconRes) { 257 final Notification.Builder builder = new Notification.Builder(mContext); 258 builder.setWhen(0); 259 builder.setSmallIcon(iconRes); 260 builder.setContentTitle(mContext.getString(titleRes)); 261 builder.setContentText(mContext.getString(R.string.vpn_lockdown_reset)); 262 builder.setContentIntent(mResetIntent); 263 builder.setPriority(Notification.PRIORITY_LOW); 264 builder.setOngoing(true); 265 NotificationManager.from(mContext).notify(TAG, 0, builder.build()); 266 } 267 268 private void hideNotification() { 269 NotificationManager.from(mContext).cancel(TAG, 0); 270 } 271} 272