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.net.NetworkCapabilities.TRANSPORT_VPN; 20 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; 21 import static android.provider.Settings.ACTION_VPN_SETTINGS; 22 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.Notification; 27 import android.app.NotificationManager; 28 import android.app.PendingIntent; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.net.ConnectivityManager; 32 import android.net.LinkAddress; 33 import android.net.LinkProperties; 34 import android.net.Network; 35 import android.net.NetworkInfo; 36 import android.net.NetworkRequest; 37 import android.os.Handler; 38 import android.text.TextUtils; 39 import android.util.Log; 40 41 import com.android.internal.R; 42 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 43 import com.android.internal.net.VpnConfig; 44 import com.android.internal.net.VpnProfile; 45 import com.android.server.connectivity.Vpn; 46 47 import java.util.List; 48 import java.util.Objects; 49 50 /** 51 * State tracker for legacy lockdown VPN. Watches for physical networks to be 52 * connected and kicks off VPN connection. 53 */ 54 public class LockdownVpnTracker { 55 private static final String TAG = "LockdownVpnTracker"; 56 57 public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; 58 59 @NonNull private final Context mContext; 60 @NonNull private final ConnectivityManager mCm; 61 @NonNull private final NotificationManager mNotificationManager; 62 @NonNull private final Handler mHandler; 63 @NonNull private final Vpn mVpn; 64 @NonNull private final VpnProfile mProfile; 65 66 @NonNull private final Object mStateLock = new Object(); 67 68 @NonNull private final PendingIntent mConfigIntent; 69 @NonNull private final PendingIntent mResetIntent; 70 71 @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback(); 72 @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback(); 73 74 private class NetworkCallback extends ConnectivityManager.NetworkCallback { 75 private Network mNetwork = null; 76 private LinkProperties mLinkProperties = null; 77 78 @Override onLinkPropertiesChanged(Network network, LinkProperties lp)79 public void onLinkPropertiesChanged(Network network, LinkProperties lp) { 80 boolean networkChanged = false; 81 if (!network.equals(mNetwork)) { 82 // The default network just changed. 83 mNetwork = network; 84 networkChanged = true; 85 } 86 mLinkProperties = lp; 87 // Backwards compatibility: previously, LockdownVpnTracker only responded to connects 88 // and disconnects, not LinkProperties changes on existing networks. 89 if (networkChanged) { 90 synchronized (mStateLock) { 91 handleStateChangedLocked(); 92 } 93 } 94 } 95 96 @Override onLost(Network network)97 public void onLost(Network network) { 98 // The default network has gone down. 99 mNetwork = null; 100 mLinkProperties = null; 101 synchronized (mStateLock) { 102 handleStateChangedLocked(); 103 } 104 } 105 getNetwork()106 public Network getNetwork() { 107 return mNetwork; 108 } 109 getLinkProperties()110 public LinkProperties getLinkProperties() { 111 return mLinkProperties; 112 } 113 } 114 115 private class VpnNetworkCallback extends NetworkCallback { 116 @Override onAvailable(Network network)117 public void onAvailable(Network network) { 118 synchronized (mStateLock) { 119 handleStateChangedLocked(); 120 } 121 } 122 @Override onLost(Network network)123 public void onLost(Network network) { 124 onAvailable(network); 125 } 126 } 127 128 @Nullable 129 private String mAcceptedEgressIface; 130 LockdownVpnTracker(@onNull Context context, @NonNull Handler handler, @NonNull Vpn vpn, @NonNull VpnProfile profile)131 public LockdownVpnTracker(@NonNull Context context, 132 @NonNull Handler handler, 133 @NonNull Vpn vpn, 134 @NonNull VpnProfile profile) { 135 mContext = Objects.requireNonNull(context); 136 mCm = mContext.getSystemService(ConnectivityManager.class); 137 mHandler = Objects.requireNonNull(handler); 138 mVpn = Objects.requireNonNull(vpn); 139 mProfile = Objects.requireNonNull(profile); 140 mNotificationManager = mContext.getSystemService(NotificationManager.class); 141 142 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS); 143 mConfigIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */, configIntent, 144 PendingIntent.FLAG_IMMUTABLE); 145 146 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET); 147 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 148 mResetIntent = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, resetIntent, 149 PendingIntent.FLAG_IMMUTABLE); 150 } 151 152 /** 153 * Watch for state changes to both active egress network, kicking off a VPN 154 * connection when ready, or setting firewall rules once VPN is connected. 155 */ handleStateChangedLocked()156 private void handleStateChangedLocked() { 157 final Network network = mDefaultNetworkCallback.getNetwork(); 158 final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties(); 159 160 final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); 161 final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); 162 163 // Restart VPN when egress network disconnected or changed 164 final boolean egressDisconnected = (network == null); 165 final boolean egressChanged = egressProp == null 166 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); 167 168 final String egressIface = (egressProp == null) ? 169 null : egressProp.getInterfaceName(); 170 Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface); 171 172 if (egressDisconnected || egressChanged) { 173 mAcceptedEgressIface = null; 174 mVpn.stopVpnRunnerPrivileged(); 175 } 176 if (egressDisconnected) { 177 hideNotification(); 178 return; 179 } 180 181 // At this point, |network| is known to be non-null. 182 if (!vpnInfo.isConnectedOrConnecting()) { 183 if (!mProfile.isValidLockdownProfile()) { 184 Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); 185 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); 186 return; 187 } 188 189 Log.d(TAG, "Active network connected; starting VPN"); 190 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); 191 192 mAcceptedEgressIface = egressIface; 193 try { 194 // Use the privileged method because Lockdown VPN is initiated by the system, so 195 // no additional permission checks are necessary. 196 // 197 // Pass in the underlying network here because the legacy VPN is, in fact, tightly 198 // coupled to a given underlying network and cannot provide mobility. This makes 199 // things marginally more correct in two ways: 200 // 201 // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra 202 // CONNECTED broadcast for the underlying network type. The underlying type comes 203 // from here. LTT *could* assume that the underlying network is the default 204 // network, but that might introduce a race condition if, say, the VPN starts 205 // connecting on cell, but when the connection succeeds and the agent is 206 // registered, the default network is now wifi. 207 // 2. If no underlying network is passed in, then CS will assume the underlying 208 // network is the system default. So, if the VPN is up and underlying network 209 // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have 210 // changed to match the new default network (e.g., cell). 211 mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp); 212 } catch (IllegalStateException e) { 213 mAcceptedEgressIface = null; 214 Log.e(TAG, "Failed to start VPN", e); 215 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); 216 } 217 } else if (vpnInfo.isConnected() && vpnConfig != null) { 218 final String iface = vpnConfig.interfaze; 219 final List<LinkAddress> sourceAddrs = vpnConfig.addresses; 220 221 Log.d(TAG, "VPN connected using iface=" + iface 222 + ", sourceAddr=" + sourceAddrs.toString()); 223 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); 224 } 225 } 226 init()227 public void init() { 228 synchronized (mStateLock) { 229 initLocked(); 230 } 231 } 232 initLocked()233 private void initLocked() { 234 Log.d(TAG, "initLocked()"); 235 236 mVpn.setEnableTeardown(false); 237 mVpn.setLockdown(true); 238 mCm.setLegacyLockdownVpnEnabled(true); 239 handleStateChangedLocked(); 240 241 mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); 242 final NetworkRequest vpnRequest = new NetworkRequest.Builder() 243 .clearCapabilities() 244 .addTransportType(TRANSPORT_VPN) 245 .build(); 246 mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler); 247 } 248 shutdown()249 public void shutdown() { 250 synchronized (mStateLock) { 251 shutdownLocked(); 252 } 253 } 254 shutdownLocked()255 private void shutdownLocked() { 256 Log.d(TAG, "shutdownLocked()"); 257 258 mAcceptedEgressIface = null; 259 260 mVpn.stopVpnRunnerPrivileged(); 261 mVpn.setLockdown(false); 262 mCm.setLegacyLockdownVpnEnabled(false); 263 hideNotification(); 264 265 mVpn.setEnableTeardown(true); 266 mCm.unregisterNetworkCallback(mDefaultNetworkCallback); 267 mCm.unregisterNetworkCallback(mVpnNetworkCallback); 268 } 269 270 /** 271 * Reset VPN lockdown tracker. Called by ConnectivityService when receiving 272 * {@link #ACTION_LOCKDOWN_RESET} pending intent. 273 */ reset()274 public void reset() { 275 Log.d(TAG, "reset()"); 276 synchronized (mStateLock) { 277 // cycle tracker, reset error count, and trigger retry 278 shutdownLocked(); 279 initLocked(); 280 handleStateChangedLocked(); 281 } 282 } 283 showNotification(int titleRes, int iconRes)284 private void showNotification(int titleRes, int iconRes) { 285 final Notification.Builder builder = 286 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN) 287 .setWhen(0) 288 .setSmallIcon(iconRes) 289 .setContentTitle(mContext.getString(titleRes)) 290 .setContentText(mContext.getString(R.string.vpn_lockdown_config)) 291 .setContentIntent(mConfigIntent) 292 .setOngoing(true) 293 .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), 294 mResetIntent) 295 .setColor(mContext.getColor( 296 com.android.internal.R.color.system_notification_accent_color)); 297 298 mNotificationManager.notify(null /* tag */, SystemMessage.NOTE_VPN_STATUS, 299 builder.build()); 300 } 301 hideNotification()302 private void hideNotification() { 303 mNotificationManager.cancel(null, SystemMessage.NOTE_VPN_STATUS); 304 } 305 } 306