blob: a2e9d67686961575106879f3674757563ba78b08 [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;
20
21import android.app.Notification;
22import android.app.NotificationManager;
23import android.app.PendingIntent;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.net.LinkProperties;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070029import android.net.LinkAddress;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070030import android.net.NetworkInfo;
31import android.net.NetworkInfo.DetailedState;
32import android.net.NetworkInfo.State;
33import android.os.INetworkManagementService;
34import android.os.RemoteException;
35import android.security.Credentials;
36import android.security.KeyStore;
37import android.text.TextUtils;
38import android.util.Slog;
39
40import com.android.internal.R;
41import com.android.internal.net.VpnConfig;
42import com.android.internal.net.VpnProfile;
43import com.android.internal.util.Preconditions;
44import com.android.server.ConnectivityService;
Jeff Sharkey91c6a642012-09-06 18:33:14 -070045import com.android.server.EventLogTags;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070046import com.android.server.connectivity.Vpn;
47
Chad Brubaker4ca19e82013-06-14 11:16:51 -070048import java.util.List;
49
Jeff Sharkey69ddab42012-08-25 00:05:46 -070050/**
51 * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
52 * connected and kicks off VPN connection, managing any required {@code netd}
53 * firewall rules.
54 */
55public class LockdownVpnTracker {
56 private static final String TAG = "LockdownVpnTracker";
57
58 /** Number of VPN attempts before waiting for user intervention. */
59 private static final int MAX_ERROR_COUNT = 4;
60
61 private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080062
Jeff Sharkey580dd312012-08-29 22:27:39 -070063 private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080064 private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
Jeff Sharkey69ddab42012-08-25 00:05:46 -070065
66 private final Context mContext;
67 private final INetworkManagementService mNetService;
68 private final ConnectivityService mConnService;
69 private final Vpn mVpn;
70 private final VpnProfile mProfile;
71
72 private final Object mStateLock = new Object();
73
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080074 private final PendingIntent mConfigIntent;
75 private final PendingIntent mResetIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070076
77 private String mAcceptedEgressIface;
78 private String mAcceptedIface;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070079 private List<LinkAddress> mAcceptedSourceAddr;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070080
81 private int mErrorCount;
82
83 public static boolean isEnabled() {
84 return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
85 }
86
87 public LockdownVpnTracker(Context context, INetworkManagementService netService,
88 ConnectivityService connService, Vpn vpn, VpnProfile profile) {
89 mContext = Preconditions.checkNotNull(context);
90 mNetService = Preconditions.checkNotNull(netService);
91 mConnService = Preconditions.checkNotNull(connService);
92 mVpn = Preconditions.checkNotNull(vpn);
93 mProfile = Preconditions.checkNotNull(profile);
94
Jeff Sharkey4fa63b22013-02-20 18:21:19 -080095 final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
96 configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
97 mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
98
Jeff Sharkey580dd312012-08-29 22:27:39 -070099 final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
100 resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
101 mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700102 }
103
104 private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
105 @Override
106 public void onReceive(Context context, Intent intent) {
107 reset();
108 }
109 };
110
111 /**
112 * Watch for state changes to both active egress network, kicking off a VPN
113 * connection when ready, or setting firewall rules once VPN is connected.
114 */
115 private void handleStateChangedLocked() {
116 Slog.d(TAG, "handleStateChanged()");
117
118 final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
119 final LinkProperties egressProp = mConnService.getActiveLinkProperties();
120
121 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
122 final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
123
124 // Restart VPN when egress network disconnected or changed
125 final boolean egressDisconnected = egressInfo == null
126 || State.DISCONNECTED.equals(egressInfo.getState());
127 final boolean egressChanged = egressProp == null
128 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
129 if (egressDisconnected || egressChanged) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700130 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700131 mAcceptedEgressIface = null;
132 mVpn.stopLegacyVpn();
133 }
Jeff Sharkey57666932013-04-30 17:01:57 -0700134 if (egressDisconnected) {
135 hideNotification();
136 return;
137 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700138
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700139 final int egressType = egressInfo.getType();
140 if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
141 EventLogTags.writeLockdownVpnError(egressType);
142 }
143
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700144 if (mErrorCount > MAX_ERROR_COUNT) {
145 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
146
147 } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
148 if (mProfile.isValidLockdownProfile()) {
149 Slog.d(TAG, "Active network connected; starting VPN");
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700150 EventLogTags.writeLockdownVpnConnecting(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700151 showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
152
153 mAcceptedEgressIface = egressProp.getInterfaceName();
Jeff Sharkey421fab82013-06-27 10:57:45 -0700154 try {
155 mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp);
156 } catch (IllegalStateException e) {
157 mAcceptedEgressIface = null;
158 Slog.e(TAG, "Failed to start VPN", e);
159 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
160 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700161 } else {
162 Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
163 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
164 }
165
166 } else if (vpnInfo.isConnected() && vpnConfig != null) {
167 final String iface = vpnConfig.interfaze;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700168 final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700169
170 if (TextUtils.equals(iface, mAcceptedIface)
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700171 && sourceAddrs.equals(mAcceptedSourceAddr)) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700172 return;
173 }
174
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700175 Slog.d(TAG, "VPN connected using iface=" + iface +
176 ", sourceAddr=" + sourceAddrs.toString());
Jeff Sharkey91c6a642012-09-06 18:33:14 -0700177 EventLogTags.writeLockdownVpnConnected(egressType);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700178 showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
179
180 try {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700181 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700182
183 mNetService.setFirewallInterfaceRule(iface, true);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700184 for (LinkAddress addr : sourceAddrs) {
185 mNetService.setFirewallEgressSourceRule(addr.toString(), true);
186 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700187
188 mErrorCount = 0;
189 mAcceptedIface = iface;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700190 mAcceptedSourceAddr = sourceAddrs;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700191 } catch (RemoteException e) {
192 throw new RuntimeException("Problem setting firewall rules", e);
193 }
194
195 mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
196 }
197 }
198
199 public void init() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700200 synchronized (mStateLock) {
201 initLocked();
202 }
203 }
204
205 private void initLocked() {
206 Slog.d(TAG, "initLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700207
208 mVpn.setEnableNotifications(false);
Jeff Sharkey57666932013-04-30 17:01:57 -0700209 mVpn.setEnableTeardown(false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700210
211 final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
212 mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
213
214 try {
215 // TODO: support non-standard port numbers
216 mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
217 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800218 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700219 } catch (RemoteException e) {
220 throw new RuntimeException("Problem setting firewall rules", e);
221 }
222
223 synchronized (mStateLock) {
224 handleStateChangedLocked();
225 }
226 }
227
228 public void shutdown() {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700229 synchronized (mStateLock) {
230 shutdownLocked();
231 }
232 }
233
234 private void shutdownLocked() {
235 Slog.d(TAG, "shutdownLocked()");
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700236
237 mAcceptedEgressIface = null;
238 mErrorCount = 0;
239
240 mVpn.stopLegacyVpn();
241 try {
242 mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
243 mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
Jeff Sharkey42c0c9f2013-02-21 10:31:45 -0800244 mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700245 } catch (RemoteException e) {
246 throw new RuntimeException("Problem setting firewall rules", e);
247 }
Jeff Sharkey580dd312012-08-29 22:27:39 -0700248 clearSourceRulesLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700249 hideNotification();
250
251 mContext.unregisterReceiver(mResetReceiver);
252 mVpn.setEnableNotifications(true);
Jeff Sharkey57666932013-04-30 17:01:57 -0700253 mVpn.setEnableTeardown(true);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700254 }
255
256 public void reset() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700257 synchronized (mStateLock) {
Jeff Sharkey580dd312012-08-29 22:27:39 -0700258 // cycle tracker, reset error count, and trigger retry
259 shutdownLocked();
260 initLocked();
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700261 handleStateChangedLocked();
262 }
263 }
264
Jeff Sharkey580dd312012-08-29 22:27:39 -0700265 private void clearSourceRulesLocked() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700266 try {
267 if (mAcceptedIface != null) {
268 mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
269 mAcceptedIface = null;
270 }
271 if (mAcceptedSourceAddr != null) {
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700272 for (LinkAddress addr : mAcceptedSourceAddr) {
273 mNetService.setFirewallEgressSourceRule(addr.toString(), false);
274 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700275 mAcceptedSourceAddr = null;
276 }
277 } catch (RemoteException e) {
278 throw new RuntimeException("Problem setting firewall rules", e);
279 }
280 }
281
282 public void onNetworkInfoChanged(NetworkInfo info) {
283 synchronized (mStateLock) {
284 handleStateChangedLocked();
285 }
286 }
287
288 public void onVpnStateChanged(NetworkInfo info) {
289 if (info.getDetailedState() == DetailedState.FAILED) {
290 mErrorCount++;
291 }
292 synchronized (mStateLock) {
293 handleStateChangedLocked();
294 }
295 }
296
297 public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
Jeff Sharkey0b81be62012-09-18 15:44:16 -0700298 if (info.isConnected()) {
299 final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
300 info = new NetworkInfo(info);
301 info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
302 }
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700303 return info;
304 }
305
306 private void showNotification(int titleRes, int iconRes) {
307 final Notification.Builder builder = new Notification.Builder(mContext);
308 builder.setWhen(0);
309 builder.setSmallIcon(iconRes);
310 builder.setContentTitle(mContext.getString(titleRes));
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800311 builder.setContentText(mContext.getString(R.string.vpn_lockdown_config));
312 builder.setContentIntent(mConfigIntent);
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700313 builder.setPriority(Notification.PRIORITY_LOW);
314 builder.setOngoing(true);
Jeff Sharkey4fa63b22013-02-20 18:21:19 -0800315 builder.addAction(
316 R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent);
317
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700318 NotificationManager.from(mContext).notify(TAG, 0, builder.build());
319 }
320
321 private void hideNotification() {
322 NotificationManager.from(mContext).cancel(TAG, 0);
323 }
324}