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 17 package com.android.server.net; 18 19 import static android.Manifest.permission.CONNECTIVITY_INTERNAL; 20 21 import android.app.Notification; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.net.LinkProperties; 29 import android.net.NetworkInfo; 30 import android.net.NetworkInfo.DetailedState; 31 import android.net.NetworkInfo.State; 32 import android.os.INetworkManagementService; 33 import android.os.RemoteException; 34 import android.security.Credentials; 35 import android.security.KeyStore; 36 import android.text.TextUtils; 37 import android.util.Slog; 38 39 import com.android.internal.R; 40 import com.android.internal.net.VpnConfig; 41 import com.android.internal.net.VpnProfile; 42 import com.android.internal.util.Preconditions; 43 import com.android.server.ConnectivityService; 44 import com.android.server.EventLogTags; 45 import com.android.server.connectivity.Vpn; 46 47 /** 48 * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be 49 * connected and kicks off VPN connection, managing any required {@code netd} 50 * firewall rules. 51 */ 52 public class LockdownVpnTracker { 53 private static final String TAG = "LockdownVpnTracker"; 54 55 /** Number of VPN attempts before waiting for user intervention. */ 56 private static final int MAX_ERROR_COUNT = 4; 57 58 private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; 59 60 private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS"; 61 private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN"; 62 63 private final Context mContext; 64 private final INetworkManagementService mNetService; 65 private final ConnectivityService mConnService; 66 private final Vpn mVpn; 67 private final VpnProfile mProfile; 68 69 private final Object mStateLock = new Object(); 70 71 private final PendingIntent mConfigIntent; 72 private final PendingIntent mResetIntent; 73 74 private String mAcceptedEgressIface; 75 private String mAcceptedIface; 76 private String mAcceptedSourceAddr; 77 78 private int mErrorCount; 79 80 public static boolean isEnabled() { 81 return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN); 82 } 83 84 public LockdownVpnTracker(Context context, INetworkManagementService netService, 85 ConnectivityService connService, Vpn vpn, VpnProfile profile) { 86 mContext = Preconditions.checkNotNull(context); 87 mNetService = Preconditions.checkNotNull(netService); 88 mConnService = Preconditions.checkNotNull(connService); 89 mVpn = Preconditions.checkNotNull(vpn); 90 mProfile = Preconditions.checkNotNull(profile); 91 92 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS); 93 configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true); 94 mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0); 95 96 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET); 97 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 98 mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0); 99 } 100 101 private BroadcastReceiver mResetReceiver = new BroadcastReceiver() { 102 @Override 103 public void onReceive(Context context, Intent intent) { 104 reset(); 105 } 106 }; 107 108 /** 109 * Watch for state changes to both active egress network, kicking off a VPN 110 * connection when ready, or setting firewall rules once VPN is connected. 111 */ 112 private void handleStateChangedLocked() { 113 Slog.d(TAG, "handleStateChanged()"); 114 115 final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered(); 116 final LinkProperties egressProp = mConnService.getActiveLinkProperties(); 117 118 final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); 119 final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); 120 121 // Restart VPN when egress network disconnected or changed 122 final boolean egressDisconnected = egressInfo == null 123 || State.DISCONNECTED.equals(egressInfo.getState()); 124 final boolean egressChanged = egressProp == null 125 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); 126 if (egressDisconnected || egressChanged) { 127 clearSourceRulesLocked(); 128 mAcceptedEgressIface = null; 129 mVpn.stopLegacyVpn(); 130 } 131 if (egressDisconnected) { 132 hideNotification(); 133 return; 134 } 135 136 final int egressType = egressInfo.getType(); 137 if (vpnInfo.getDetailedState() == DetailedState.FAILED) { 138 EventLogTags.writeLockdownVpnError(egressType); 139 } 140 141 if (mErrorCount > MAX_ERROR_COUNT) { 142 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); 143 144 } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) { 145 if (mProfile.isValidLockdownProfile()) { 146 Slog.d(TAG, "Active network connected; starting VPN"); 147 EventLogTags.writeLockdownVpnConnecting(egressType); 148 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); 149 150 mAcceptedEgressIface = egressProp.getInterfaceName(); 151 mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp); 152 153 } else { 154 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); 155 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); 156 } 157 158 } else if (vpnInfo.isConnected() && vpnConfig != null) { 159 final String iface = vpnConfig.interfaze; 160 final String sourceAddr = vpnConfig.addresses; 161 162 if (TextUtils.equals(iface, mAcceptedIface) 163 && TextUtils.equals(sourceAddr, mAcceptedSourceAddr)) { 164 return; 165 } 166 167 Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr); 168 EventLogTags.writeLockdownVpnConnected(egressType); 169 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); 170 171 try { 172 clearSourceRulesLocked(); 173 174 mNetService.setFirewallInterfaceRule(iface, true); 175 mNetService.setFirewallEgressSourceRule(sourceAddr, true); 176 177 mErrorCount = 0; 178 mAcceptedIface = iface; 179 mAcceptedSourceAddr = sourceAddr; 180 } catch (RemoteException e) { 181 throw new RuntimeException("Problem setting firewall rules", e); 182 } 183 184 mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo)); 185 } 186 } 187 188 public void init() { 189 synchronized (mStateLock) { 190 initLocked(); 191 } 192 } 193 194 private void initLocked() { 195 Slog.d(TAG, "initLocked()"); 196 197 mVpn.setEnableNotifications(false); 198 mVpn.setEnableTeardown(false); 199 200 final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET); 201 mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null); 202 203 try { 204 // TODO: support non-standard port numbers 205 mNetService.setFirewallEgressDestRule(mProfile.server, 500, true); 206 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true); 207 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true); 208 } catch (RemoteException e) { 209 throw new RuntimeException("Problem setting firewall rules", e); 210 } 211 212 synchronized (mStateLock) { 213 handleStateChangedLocked(); 214 } 215 } 216 217 public void shutdown() { 218 synchronized (mStateLock) { 219 shutdownLocked(); 220 } 221 } 222 223 private void shutdownLocked() { 224 Slog.d(TAG, "shutdownLocked()"); 225 226 mAcceptedEgressIface = null; 227 mErrorCount = 0; 228 229 mVpn.stopLegacyVpn(); 230 try { 231 mNetService.setFirewallEgressDestRule(mProfile.server, 500, false); 232 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false); 233 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false); 234 } catch (RemoteException e) { 235 throw new RuntimeException("Problem setting firewall rules", e); 236 } 237 clearSourceRulesLocked(); 238 hideNotification(); 239 240 mContext.unregisterReceiver(mResetReceiver); 241 mVpn.setEnableNotifications(true); 242 mVpn.setEnableTeardown(true); 243 } 244 245 public void reset() { 246 synchronized (mStateLock) { 247 // cycle tracker, reset error count, and trigger retry 248 shutdownLocked(); 249 initLocked(); 250 handleStateChangedLocked(); 251 } 252 } 253 254 private void clearSourceRulesLocked() { 255 try { 256 if (mAcceptedIface != null) { 257 mNetService.setFirewallInterfaceRule(mAcceptedIface, false); 258 mAcceptedIface = null; 259 } 260 if (mAcceptedSourceAddr != null) { 261 mNetService.setFirewallEgressSourceRule(mAcceptedSourceAddr, false); 262 mAcceptedSourceAddr = null; 263 } 264 } catch (RemoteException e) { 265 throw new RuntimeException("Problem setting firewall rules", e); 266 } 267 } 268 269 public void onNetworkInfoChanged(NetworkInfo info) { 270 synchronized (mStateLock) { 271 handleStateChangedLocked(); 272 } 273 } 274 275 public void onVpnStateChanged(NetworkInfo info) { 276 if (info.getDetailedState() == DetailedState.FAILED) { 277 mErrorCount++; 278 } 279 synchronized (mStateLock) { 280 handleStateChangedLocked(); 281 } 282 } 283 284 public NetworkInfo augmentNetworkInfo(NetworkInfo info) { 285 if (info.isConnected()) { 286 final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); 287 info = new NetworkInfo(info); 288 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); 289 } 290 return info; 291 } 292 293 private void showNotification(int titleRes, int iconRes) { 294 final Notification.Builder builder = new Notification.Builder(mContext); 295 builder.setWhen(0); 296 builder.setSmallIcon(iconRes); 297 builder.setContentTitle(mContext.getString(titleRes)); 298 builder.setContentText(mContext.getString(R.string.vpn_lockdown_config)); 299 builder.setContentIntent(mConfigIntent); 300 builder.setPriority(Notification.PRIORITY_LOW); 301 builder.setOngoing(true); 302 builder.addAction( 303 R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent); 304 305 NotificationManager.from(mContext).notify(TAG, 0, builder.build()); 306 } 307 308 private void hideNotification() { 309 NotificationManager.from(mContext).cancel(TAG, 0); 310 } 311 } 312