blob: ef8f6479cb3d19292f4b293b1a7a4c7dbf85a90c [file] [log] [blame]
Jeff Sharkey69ddab42012-08-25 00:05:46 -07001/*
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
paulhu59148b72019-08-12 16:25:11 +080019import static android.Manifest.permission.NETWORK_STACK;
Robin Leea249aee2016-02-03 13:42:56 +000020import static android.provider.Settings.ACTION_VPN_SETTINGS;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070021
junyulai465088e2019-08-30 16:44:45 +080022import android.annotation.NonNull;
23import android.annotation.Nullable;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070024import 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;
Lorenzo Colitti0cb79032014-10-15 16:06:07 +090031import android.net.ConnectivityManager;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070032import android.net.LinkAddress;
Remi NGUYEN VANdacee142019-02-13 18:28:35 +090033import android.net.LinkProperties;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070034import android.net.NetworkInfo;
35import android.net.NetworkInfo.DetailedState;
36import android.net.NetworkInfo.State;
junyulai465088e2019-08-30 16:44:45 +080037import android.os.Handler;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070038import android.security.Credentials;
39import android.security.KeyStore;
40import android.text.TextUtils;
41import android.util.Slog;
42
43import com.android.internal.R;
Chris Wren282cfef2017-03-27 15:01:44 -040044import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070045import com.android.internal.net.VpnConfig;
46import com.android.internal.net.VpnProfile;
Geoffrey Pitschaf759c52017-02-15 09:35:38 -050047import com.android.internal.notification.SystemNotificationChannels;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070048import com.android.server.ConnectivityService;
Jeff Sharkey91c6a642012-09-06 18:33:14 -070049import com.android.server.EventLogTags;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070050import com.android.server.connectivity.Vpn;
51
Chad Brubaker4ca19e82013-06-14 11:16:51 -070052import java.util.List;
Daulet Zhanguzin8d0b7f12020-01-03 13:37:24 +000053import java.util.Objects;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070054
Jeff Sharkey69ddab42012-08-25 00:05:46 -070055/**
56 * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
57 * connected and kicks off VPN connection, managing any required {@code netd}
58 * firewall rules.
59 */
60public class LockdownVpnTracker {
61 private static final String TAG = "LockdownVpnTracker";
62
63 /** Number of VPN attempts before waiting for user intervention. */
64 private static final int MAX_ERROR_COUNT = 4;
65
66 private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080067
junyulai465088e2019-08-30 16:44:45 +080068 @NonNull private final Context mContext;
69 @NonNull private final ConnectivityService mConnService;
70 @NonNull private final Handler mHandler;
71 @NonNull private final Vpn mVpn;
72 @NonNull private final VpnProfile mProfile;
Lorenzo Colittiad4cd0c2014-10-16 01:06:29 +090073
junyulai465088e2019-08-30 16:44:45 +080074 @NonNull private final Object mStateLock = new Object();
Jeff Sharkey69ddab42012-08-25 00:05:46 -070075
junyulai465088e2019-08-30 16:44:45 +080076 @NonNull private final PendingIntent mConfigIntent;
77 @NonNull private final PendingIntent mResetIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070078
junyulai465088e2019-08-30 16:44:45 +080079 @Nullable
Jeff Sharkey69ddab42012-08-25 00:05:46 -070080 private String mAcceptedEgressIface;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070081
82 private int mErrorCount;
83
84 public static boolean isEnabled() {
85 return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
86 }
87
junyulai465088e2019-08-30 16:44:45 +080088 public LockdownVpnTracker(@NonNull Context context,
89 @NonNull ConnectivityService connService,
90 @NonNull Handler handler,
91 @NonNull Vpn vpn,
92 @NonNull VpnProfile profile) {
Daulet Zhanguzin8d0b7f12020-01-03 13:37:24 +000093 mContext = Objects.requireNonNull(context);
94 mConnService = Objects.requireNonNull(connService);
95 mHandler = Objects.requireNonNull(handler);
96 mVpn = Objects.requireNonNull(vpn);
97 mProfile = Objects.requireNonNull(profile);
Jeff Sharkey69ddab42012-08-25 00:05:46 -070098
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080099 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800100 mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
101
Jeff Sharkey580dd312012-08-29 22:27:39 -0700102 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
103 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
104 mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700105 }
106
107 private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
108 @Override
109 public void onReceive(Context context, Intent intent) {
110 reset();
111 }
112 };
113
114 /**
115 * Watch for state changes to both active egress network, kicking off a VPN
116 * connection when ready, or setting firewall rules once VPN is connected.
117 */
118 private void handleStateChangedLocked() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700119
120 final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
121 final LinkProperties egressProp = mConnService.getActiveLinkProperties();
122
123 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
124 final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
125
126 // Restart VPN when egress network disconnected or changed
127 final boolean egressDisconnected = egressInfo == null
128 || State.DISCONNECTED.equals(egressInfo.getState());
129 final boolean egressChanged = egressProp == null
130 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900131
132 final String egressTypeName = (egressInfo == null) ?
133 null : ConnectivityManager.getNetworkTypeName(egressInfo.getType());
134 final String egressIface = (egressProp == null) ?
135 null : egressProp.getInterfaceName();
136 Slog.d(TAG, "handleStateChanged: egress=" + egressTypeName +
137 " " + mAcceptedEgressIface + "->" + egressIface);
138
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700139 if (egressDisconnected || egressChanged) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700140 mAcceptedEgressIface = null;
Jeff Davidsonb21298a2015-02-10 10:02:11 -0800141 mVpn.stopLegacyVpnPrivileged();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700142 }
Jeff Sharkey57666932013-04-30 17:01:57 -0700143 if (egressDisconnected) {
144 hideNotification();
145 return;
146 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700147
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700148 final int egressType = egressInfo.getType();
149 if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
150 EventLogTags.writeLockdownVpnError(egressType);
151 }
152
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700153 if (mErrorCount > MAX_ERROR_COUNT) {
154 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
155
156 } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
157 if (mProfile.isValidLockdownProfile()) {
158 Slog.d(TAG, "Active network connected; starting VPN");
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700159 EventLogTags.writeLockdownVpnConnecting(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700160 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
161
162 mAcceptedEgressIface = egressProp.getInterfaceName();
Jeff Sharkey421fab82013-06-27 10:57:45 -0700163 try {
Jeff Davidsonb21298a2015-02-10 10:02:11 -0800164 // Use the privileged method because Lockdown VPN is initiated by the system, so
165 // no additional permission checks are necessary.
166 mVpn.startLegacyVpnPrivileged(mProfile, KeyStore.getInstance(), egressProp);
Jeff Sharkey421fab82013-06-27 10:57:45 -0700167 } catch (IllegalStateException e) {
168 mAcceptedEgressIface = null;
169 Slog.e(TAG, "Failed to start VPN", e);
170 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
171 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700172 } else {
173 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
174 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
175 }
176
177 } else if (vpnInfo.isConnected() && vpnConfig != null) {
178 final String iface = vpnConfig.interfaze;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700179 final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700180
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700181 Slog.d(TAG, "VPN connected using iface=" + iface +
182 ", sourceAddr=" + sourceAddrs.toString());
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700183 EventLogTags.writeLockdownVpnConnected(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700184 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
185
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600186 final NetworkInfo clone = new NetworkInfo(egressInfo);
187 augmentNetworkInfo(clone);
188 mConnService.sendConnectedBroadcast(clone);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700189 }
190 }
191
192 public void init() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700193 synchronized (mStateLock) {
194 initLocked();
195 }
196 }
197
198 private void initLocked() {
199 Slog.d(TAG, "initLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700200
Jeff Sharkey57666932013-04-30 17:01:57 -0700201 mVpn.setEnableTeardown(false);
Robin Leec3736bc2017-03-10 16:19:54 +0000202 mVpn.setLockdown(true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700203
204 final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
paulhu59148b72019-08-12 16:25:11 +0800205 mContext.registerReceiver(mResetReceiver, resetFilter, NETWORK_STACK, mHandler);
Robin Leeaca5e7e32015-11-12 10:28:06 +0000206 handleStateChangedLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700207 }
208
209 public void shutdown() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700210 synchronized (mStateLock) {
211 shutdownLocked();
212 }
213 }
214
215 private void shutdownLocked() {
216 Slog.d(TAG, "shutdownLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700217
218 mAcceptedEgressIface = null;
219 mErrorCount = 0;
220
Jeff Davidsonb21298a2015-02-10 10:02:11 -0800221 mVpn.stopLegacyVpnPrivileged();
Robin Leec3736bc2017-03-10 16:19:54 +0000222 mVpn.setLockdown(false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700223 hideNotification();
224
225 mContext.unregisterReceiver(mResetReceiver);
Jeff Sharkey57666932013-04-30 17:01:57 -0700226 mVpn.setEnableTeardown(true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700227 }
228
229 public void reset() {
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900230 Slog.d(TAG, "reset()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700231 synchronized (mStateLock) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700232 // cycle tracker, reset error count, and trigger retry
233 shutdownLocked();
234 initLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700235 handleStateChangedLocked();
236 }
237 }
238
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900239 public void onNetworkInfoChanged() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700240 synchronized (mStateLock) {
241 handleStateChangedLocked();
242 }
243 }
244
245 public void onVpnStateChanged(NetworkInfo info) {
246 if (info.getDetailedState() == DetailedState.FAILED) {
247 mErrorCount++;
248 }
249 synchronized (mStateLock) {
250 handleStateChangedLocked();
251 }
252 }
253
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600254 public void augmentNetworkInfo(NetworkInfo info) {
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700255 if (info.isConnected()) {
256 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700257 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
258 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700259 }
260
261 private void showNotification(int titleRes, int iconRes) {
Geoffrey Pitschaf759c52017-02-15 09:35:38 -0500262 final Notification.Builder builder =
263 new Notification.Builder(mContext, SystemNotificationChannels.VPN)
264 .setWhen(0)
265 .setSmallIcon(iconRes)
266 .setContentTitle(mContext.getString(titleRes))
267 .setContentText(mContext.getString(R.string.vpn_lockdown_config))
268 .setContentIntent(mConfigIntent)
269 .setOngoing(true)
270 .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
271 mResetIntent)
272 .setColor(mContext.getColor(
273 com.android.internal.R.color.system_notification_accent_color));
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800274
Chris Wren282cfef2017-03-27 15:01:44 -0400275 NotificationManager.from(mContext).notify(null, SystemMessage.NOTE_VPN_STATUS,
276 builder.build());
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700277 }
278
279 private void hideNotification() {
Chris Wren282cfef2017-03-27 15:01:44 -0400280 NotificationManager.from(mContext).cancel(null, SystemMessage.NOTE_VPN_STATUS);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700281 }
282}