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