blob: 4a8539aa328279a748633d14eb4035392f48224c [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
19import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
Xiaohui Chenb41c9f72015-06-17 15:55:37 -070020import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
Amith Yamasani15e472352015-04-24 19:06:07 -070021import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
22import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
Robin Leea249aee2016-02-03 13:42:56 +000023import static android.provider.Settings.ACTION_VPN_SETTINGS;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070024
25import android.app.Notification;
26import android.app.NotificationManager;
27import android.app.PendingIntent;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
Lorenzo Colitti0cb79032014-10-15 16:06:07 +090032import android.net.ConnectivityManager;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070033import android.net.LinkProperties;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070034import android.net.LinkAddress;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070035import android.net.NetworkInfo;
36import android.net.NetworkInfo.DetailedState;
37import android.net.NetworkInfo.State;
Amith Yamasani15e472352015-04-24 19:06:07 -070038import android.net.NetworkPolicyManager;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070039import android.os.INetworkManagementService;
40import android.os.RemoteException;
41import android.security.Credentials;
42import android.security.KeyStore;
Lorenzo Colittiad4cd0c2014-10-16 01:06:29 +090043import android.system.Os;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070044import android.text.TextUtils;
45import android.util.Slog;
46
47import com.android.internal.R;
48import com.android.internal.net.VpnConfig;
49import com.android.internal.net.VpnProfile;
50import com.android.internal.util.Preconditions;
51import com.android.server.ConnectivityService;
Jeff Sharkey91c6a642012-09-06 18:33:14 -070052import com.android.server.EventLogTags;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070053import com.android.server.connectivity.Vpn;
54
Chad Brubaker4ca19e82013-06-14 11:16:51 -070055import java.util.List;
56
Jeff Sharkey69ddab42012-08-25 00:05:46 -070057/**
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 */
62public 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";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080069
Lorenzo Colittiad4cd0c2014-10-16 01:06:29 +090070 private static final int ROOT_UID = 0;
71
Jeff Sharkey69ddab42012-08-25 00:05:46 -070072 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
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080080 private final PendingIntent mConfigIntent;
81 private final PendingIntent mResetIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070082
83 private String mAcceptedEgressIface;
84 private String mAcceptedIface;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070085 private List<LinkAddress> mAcceptedSourceAddr;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070086
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
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800101 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800102 mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
103
Jeff Sharkey580dd312012-08-29 22:27:39 -0700104 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
105 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
106 mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700107 }
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() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700121
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());
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900133
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
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700141 if (egressDisconnected || egressChanged) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700142 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700143 mAcceptedEgressIface = null;
Jeff Davidsonb21298a2015-02-10 10:02:11 -0800144 mVpn.stopLegacyVpnPrivileged();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700145 }
Jeff Sharkey57666932013-04-30 17:01:57 -0700146 if (egressDisconnected) {
147 hideNotification();
148 return;
149 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700150
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700151 final int egressType = egressInfo.getType();
152 if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
153 EventLogTags.writeLockdownVpnError(egressType);
154 }
155
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700156 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");
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700162 EventLogTags.writeLockdownVpnConnecting(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700163 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
164
165 mAcceptedEgressIface = egressProp.getInterfaceName();
Jeff Sharkey421fab82013-06-27 10:57:45 -0700166 try {
Jeff Davidsonb21298a2015-02-10 10:02:11 -0800167 // 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);
Jeff Sharkey421fab82013-06-27 10:57:45 -0700170 } 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 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700175 } 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;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700182 final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700183
184 if (TextUtils.equals(iface, mAcceptedIface)
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700185 && sourceAddrs.equals(mAcceptedSourceAddr)) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700186 return;
187 }
188
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700189 Slog.d(TAG, "VPN connected using iface=" + iface +
190 ", sourceAddr=" + sourceAddrs.toString());
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700191 EventLogTags.writeLockdownVpnConnected(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700192 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
193
194 try {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700195 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700196
197 mNetService.setFirewallInterfaceRule(iface, true);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700198 for (LinkAddress addr : sourceAddrs) {
Lorenzo Colitti02c7aba2014-10-16 00:55:07 +0900199 setFirewallEgressSourceRule(addr, true);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700200 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700201
Xiaohui Chenb41c9f72015-06-17 15:55:37 -0700202 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
203 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);
Lorenzo Colittiad4cd0c2014-10-16 01:06:29 +0900204
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700205 mErrorCount = 0;
206 mAcceptedIface = iface;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700207 mAcceptedSourceAddr = sourceAddrs;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700208 } catch (RemoteException e) {
209 throw new RuntimeException("Problem setting firewall rules", e);
210 }
211
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600212 final NetworkInfo clone = new NetworkInfo(egressInfo);
213 augmentNetworkInfo(clone);
214 mConnService.sendConnectedBroadcast(clone);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700215 }
216 }
217
218 public void init() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700219 synchronized (mStateLock) {
220 initLocked();
221 }
222 }
223
224 private void initLocked() {
225 Slog.d(TAG, "initLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700226
Jeff Sharkey57666932013-04-30 17:01:57 -0700227 mVpn.setEnableTeardown(false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700228
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);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800236 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700237 } catch (RemoteException e) {
238 throw new RuntimeException("Problem setting firewall rules", e);
239 }
240
Robin Leeaca5e7e32015-11-12 10:28:06 +0000241 handleStateChangedLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700242 }
243
244 public void shutdown() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700245 synchronized (mStateLock) {
246 shutdownLocked();
247 }
248 }
249
250 private void shutdownLocked() {
251 Slog.d(TAG, "shutdownLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700252
253 mAcceptedEgressIface = null;
254 mErrorCount = 0;
255
Jeff Davidsonb21298a2015-02-10 10:02:11 -0800256 mVpn.stopLegacyVpnPrivileged();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700257 try {
258 mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
259 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800260 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700261 } catch (RemoteException e) {
262 throw new RuntimeException("Problem setting firewall rules", e);
263 }
Jeff Sharkey580dd312012-08-29 22:27:39 -0700264 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700265 hideNotification();
266
267 mContext.unregisterReceiver(mResetReceiver);
Jeff Sharkey57666932013-04-30 17:01:57 -0700268 mVpn.setEnableTeardown(true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700269 }
270
271 public void reset() {
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900272 Slog.d(TAG, "reset()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700273 synchronized (mStateLock) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700274 // cycle tracker, reset error count, and trigger retry
275 shutdownLocked();
276 initLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700277 handleStateChangedLocked();
278 }
279 }
280
Jeff Sharkey580dd312012-08-29 22:27:39 -0700281 private void clearSourceRulesLocked() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700282 try {
283 if (mAcceptedIface != null) {
284 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
285 mAcceptedIface = null;
286 }
287 if (mAcceptedSourceAddr != null) {
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700288 for (LinkAddress addr : mAcceptedSourceAddr) {
Lorenzo Colitti02c7aba2014-10-16 00:55:07 +0900289 setFirewallEgressSourceRule(addr, false);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700290 }
Lorenzo Colittiad4cd0c2014-10-16 01:06:29 +0900291
Xiaohui Chenb41c9f72015-06-17 15:55:37 -0700292 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
293 mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);
Lorenzo Colittiad4cd0c2014-10-16 01:06:29 +0900294
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700295 mAcceptedSourceAddr = null;
296 }
297 } catch (RemoteException e) {
298 throw new RuntimeException("Problem setting firewall rules", e);
299 }
300 }
301
Lorenzo Colitti02c7aba2014-10-16 00:55:07 +0900302 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
Lorenzo Colitti0cb79032014-10-15 16:06:07 +0900310 public void onNetworkInfoChanged() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700311 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
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600325 public void augmentNetworkInfo(NetworkInfo info) {
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700326 if (info.isConnected()) {
327 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700328 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
329 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700330 }
331
332 private void showNotification(int titleRes, int iconRes) {
Selim Cinek255dd042014-08-19 22:29:02 +0200333 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)
Alan Viverette4a357cd2015-03-18 18:37:18 -0700343 .setColor(mContext.getColor(
Selim Cinek255dd042014-08-19 22:29:02 +0200344 com.android.internal.R.color.system_notification_accent_color));
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800345
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700346 NotificationManager.from(mContext).notify(TAG, 0, builder.build());
347 }
348
349 private void hideNotification() {
350 NotificationManager.from(mContext).cancel(TAG, 0);
351 }
352}