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