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