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