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